Android基础之Fragment | setMaxLifecycle | 延迟加载 | 实例 | 带源码地址

Fragment

何为Fragment

是一种可以嵌入在活动中的UI片段,又称作碎片,需要嵌套在Activity中使用,因此生命周期和其Activity是相关联的

  • 以下是官方说法

A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running.

  • 由此可知,Fragment ------ Activity (多对多关系)

优势

  • 模块化(Modularity)每个模块的代码相互独立
  • 可重用(Reusability)减少冗余
  • 可适配(Adaptability)不同尺寸不同布局

Fragment && Activity 生命周期对比

有关的核心类

  • Fragment
  • FragmentManager(管理和维护Fragment,是抽象类,具体实现是FragmentManagerImpl
  • FragmentTransaction(对Fragemnt进行增删等,抽象类,具体实现类是BackStackRecord)

加载方式

静态加载

  • fragment_left
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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:background="@color/black">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is left Fragment"
        android:textColor="@color/white"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
  • fragment_right
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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:background="@color/white">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="This is right Fragment"
        android:textColor="@color/black"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
  • LeftFragment
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.fragment_left,container,false);
        return view;
    }
}
  • 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.fragment_right,container,false);
        return view;
    }
}
  • MainActivity
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
  • activity_main
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

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

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

</androidx.constraintlayout.widget.ConstraintLayout>

动态加载

  • MainActivity
public class MainActivity extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        displayFragment(new RightFragment(), new LeftFragment());
    }

    private void displayFragment(Fragment leftFragment, Fragment rightFragment) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        //开启一个事务
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(R.id.left_fragment,leftFragment);
        transaction.replace(R.id.right_fragment,rightFragment);
        transaction.commit();
    }

}
  • xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/left_fragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/right_fragment"/>

    <FrameLayout
        android:id="@+id/right_fragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toEndOf="@id/left_fragment"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

简单实例(一个简易版的新闻应用)

  • 创建一个实体类News,两个参数,title和content,title代表新闻标题,content代表新闻内容
public class News {

    private String title;
    private String content;

    public String getTitle() {
        return title;
    }

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

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}
  • 分别在layout/layout-sw600dp中新建布局文件acticity_main,根据不同的屏幕大小,系统去调用不同的布局文件

layout/activity_main

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

layout-sw600dp/activity_main

在当中,我们将content的fragment碎片放在了帧布局framelayout中,这样,我们之后在title帧布局中可以根据判断所在的activity是否有帧布局的id来判断屏幕大小

<fragment
          android:id="@+id/news_title_fragment"
          android:name="com.example.fragmentbestpractice.activity.NewsTitleFragment"
          android:layout_width="0dp"
          android:layout_height="match_parent"
          app:layout_constraintWidth_default="percent"
          app:layout_constraintWidth_percent="0.25"
          app:layout_constraintBottom_toBottomOf="parent"
          app:layout_constraintStart_toStartOf="parent"
          app:layout_constraintTop_toTopOf="parent" />

<FrameLayout
             android:id="@+id/news_content_layout"
             android:layout_width="0dp"
             android:layout_height="match_parent"
             app:layout_constraintWidth_default="percent"
             app:layout_constraintWidth_percent="0.75"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintHorizontal_weight="3"
             app:layout_constraintStart_toEndOf="@+id/news_title_fragment"
             app:layout_constraintTop_toTopOf="parent">

    	<fragment
              android:id="@+id/news_content_fragment"
              android:name="com.example.fragmentbestpractice.activity.NewsContentFragment"
              android:layout_width="match_parent"
              android:layout_height="match_parent" />
</FrameLayout>
  • 创建news_content_frag.xml文件,作为新闻内容的布局

主要分为两部分,头部显示新闻标题,正文部分显示新闻内容,中间使用一条细线(view)隔开

<LinearLayout
              android:id="@+id/visibility_layout"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical"
              android:visibility="invisible">

  <TextView
            android:id="@+id/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/news_content"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:padding="15dp"
            android:textSize="18sp" />

</LinearLayout>

<View
      android:layout_width="1dp"
      android:layout_height="match_parent"
      android:background="#000"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />
  • 创建NewsContentFragment类,用来加载该布局文件

提供了一个refresh()方法,用于将新闻的标题和内容显示在界面上

public class NewsContentFragment extends Fragment {

  	private View view;

	  @Nullable
 	 	@Override
  	public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    		view = inflater.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);

    		TextView newsTitleText = view.findViewById(R.id.news_title);
    		TextView newsContentText = view.findViewById(R.id.news_content);

    		newsTitleText.setText(newsTitle);
    		newsContentText.setText(newsContent);

  	}
}
  • 创建一个单独的news_content.xml文件用来单页显示NewsContentFragment碎片

里面只有一个NewsContentFragment碎片

<fragment
        android:id="@+id/news_content_fragment"
        android:name="com.example.fragmentbestpractice.activity.NewsContentFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
  • 创建一个单独的活动NewsContentActivity用来加载news_content

创建了一个actionActivity方法,实现低耦合,然后将刷新的数据传给NewsContentFragment的refresh方法,用于显示数据

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(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.news_content);

        String newsTitle = getIntent().getStringExtra("news_title");
        String newsContent = getIntent().getStringExtra("news_content");

        NewsContentFragment newsContentFragment = (NewsContentFragment) getSupportFragmentManager().findFragmentById(R.id.news_content_fragment);
        newsContentFragment.refresh(newsTitle, newsContent);
    }
}
  • 创建一个显示新闻标题的布局news_title_frag.xml

里面只有一个用于显示新闻标题列表的RecyclerView,既然要显示其,必会定义其子布局

<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/news_title_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
  • 定义RecyclerView的子布局文件news_item.xml

其中只有一项组件TextView,padding表示给控件的周边补白,这样不至于让文本仅靠边缘,ellipsize用于设定当文本内容超出控件宽度时,文本在尾部进行缩略

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
	    android:id="@+id/news_title"
	    android:layout_width="match_parent"
	    android:layout_height="wrap_content"
	    android:maxLines="1"
	    android:ellipsize="end"
	    android:textSize="18sp"
	    android:paddingLeft="10dp"
	    android:paddingRight="10dp"
	    android:paddingTop="15dp"
	    android:paddingBottom="15dp"/>
  • 新建NewsTitleFragment用于展示新闻列表的碎片news_title_frag

首先在onCreateView()加载了news_title_frag布局,定义参数isTwoPane,在onActivityCreated()中根据帧布局id:news_content_layout来判断屏幕处于单页还是双页模式,给isTwoPane赋值(onCreateView()/onActivityCreated())

其次,通过RecyclerView将列表展示出来,在该类中新建一个内部类NewsAdapter来作为RecyclerView的适配器,将标题展示出来(class NewsAdapter)

最后,向RecyclerView中填充数据(onCreateView()/getNews()/getRandomLengthContent())

public class NewsTitleFragment extends Fragment {

    private boolean isTwoPane;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.news_title_frag,container,false);

        RecyclerView newsTitleRecyclerView = view.findViewById(R.id.news_title_recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
        newsTitleRecyclerView.setLayoutManager(layoutManager);

        NewsAdapter adapter = new NewsAdapter(getNews());
        newsTitleRecyclerView.setAdapter(adapter);

        return view;
    }

    private List<News> getNews() {
        List<News> newsList = new ArrayList<>();

        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(30) + 1;
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            builder.append(content);
        }
        return builder.toString();
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (getActivity().findViewById(R.id.news_content_layout) != null) {
            // 可以找到news_content_layout布局时,为双页模式
            isTwoPane = true;
        } else {
            // 找不到news_content_layout布局时,为单页模式
            isTwoPane = false;
        }
    }

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

        private List<News> mNewsList;

        class ViewHolder extends RecyclerView.ViewHolder {

            TextView newsTitleText;

            public ViewHolder(View view) {
                super(view);
                newsTitleText = view.findViewById(R.id.news_title);
            }
        }

        public NewsAdapter(List<News> newsList) {
            mNewsList = newsList;
        }

        @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull 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 view) {
                    News news = mNewsList.get(holder.getAdapterPosition());

                    if(isTwoPane) {
                        // 如果是双页模式,则刷新NewsContentFragment中的内容
                        NewsContentFragment newsContentFragment = (NewsContentFragment) getFragmentManager().findFragmentById(R.id.news_content_fragment);
                        newsContentFragment.refresh(news.getTitle(),news.getContent());
                    } else {
                        // 如果是单页模式,则直接启动NewsContentActicity
                        NewsContentActivity.actionStart(getActivity(),news.getTitle(),news.getContent());
                    }
                }
            });
            return holder;
        }

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

        @Override
        public int getItemCount() {
            return mNewsList.size();
        }
    }
}
  • 源码地址

DoSomeAndroidTest/MyFragmentTest at main · qricis/DoSomeAndroidTest · GitHub

FragmentManager

  • 管理其宿主(Activity/Fragment)中的fragments,通过getSupportFragmentManager() | getChildFragmentMnager()获得
  • getFragmentManager()所得到的是所在Fragment 的父容器(Activity)的管理器

  • getSupportFragmentManager()就是获取当前Activity中,用来管理Fragment的管理器

  • getChildFragmentManager()所得到的是该Fragment,用来管理子fragments的管理器

  • 需要管理相互独立的并且隶属于Activity的Fragment使用getFragmentManager(),而在Fragment中动态的添加Fragment要使用getChildFragmetManager()来管理。

一些常用到的方法

  • getFragments() 返回容器中所有fragment的列表
  • getBackStackEntryCount() 返回栈中所有的fragment个数
  • popBackStack()  栈顶元素出栈
  • beginTransaction() 获取FragmentTransaction
  • findFragmentByTag() 根据TAG找到对应的fragment,主要用于动态添加中
  • findFragmentById() 同上,主要用于静态

FragmentTransaction

  • 对fragment进行一些事务操作
FragmentTransaction transaction = fragmentManager.beginTransaction();

常用方法

  • add() 添加一个fragment
  • remove() 移出一个fragment 若该fragment没有被加入回退栈,则该实例会被销毁
  • replace() 使用一个fragment替换当前的fragment(remove() + add() )
  • hide() 隐藏当前Fragment,但不会销毁
  • show() 显示之前隐藏的Fragment
  • detach() 将view从UI移除,和remove()不同的是,此时fragment的状态依然由FragmentManager维护
  • attach() 重建view视图,附加到UI上并显示
  • addToBackStack() 将Fragment添加进回退栈
  • commit()

Schedules a commit of this transaction. The commit does not happen immediately; it will be scheduled as work on the main thread to be done the next time that thread is ready.

A transaction can only be committed with this method prior to its containing activity saving its state.  If the commit is attempted after that point, an exception will be thrown.  This is because the state after the commit can be lost if the activity needs to be restored from its state.  See {@link  #commitAllowingStateLoss()} for situations where it may be okay to lose the commit.

  • commitAllowingStateLoss()
  • commitNow()

Commits this transaction synchronously. Any added fragments will be initialized and brought completely to the lifecycle state of their host and any removed fragments will be torn down accordingly before this call returns.

A transaction can only be committed with this method prior to its containing activity saving its state.  If the commit is attempted after that point, an exception will be thrown.  This is because the state after the commit can be lost if the activity needs to be restored from its state.  See {@link  #commitAllowingStateLoss()} for situations where it may be okay to lose the commit.

  • commitNowAllowingStateLoss()

Fragment的参数传递

  • 如果Activity中包含Fragment对象,可以直接访问其所有的public方法

Bundle()方法(Activity--->Fragment)

  • MainActivity
public class MainActivity extends AppCompatActivity {
    private Fragment mLeftFragment;
    private Fragment mRightFragment;

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

        init();

        displayFragment(R.id.left_frame_layout, mLeftFragment);
        displayFragment(R.id.right_frame_layout, mRightFragment);

        setNewText(mRightFragment, "right! right! right!");
        setNewText(mLeftFragment, "left! left! left!");
    }

    private void init() {
        mLeftFragment = new LeftFragment();
        mRightFragment = new RightFragment();
    }

    private void setNewText(Fragment fragment, String str) {
        Bundle bundle = new Bundle();
        bundle.putString(fragment.getClass().getSimpleName(), str);
        fragment.setArguments(bundle);
    }

    private void displayFragment(int id, Fragment fragment) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        //开启一个事务
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(id, fragment);
        transaction.addToBackStack(fragment.getClass().getSimpleName());
        transaction.commitAllowingStateLoss();
    }

}
  • LeftFragment
public class LeftFragment extends Fragment {

    private TextView mTextView;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_left,container,false);
        mTextView = view.findViewById(R.id.left_fragment_textView);
        checkBundle();
        return view;
    }

    /**
     * activity是否有传递数据
     */
    private void checkBundle() {
        Bundle bundle = this.getArguments();
        if (bundle != null) {
            String str = bundle.getString(this.getClass().getSimpleName());
            mTextView.setText(str);
        }
    }
}

回调接口(Fragment--->Activity)

  • MyFragmentListener接口
public interface MyFragmentListener {
    /**
     * 任意给activity传递参数
     * @param str 传递的参数
     */
    void whatFragmentSay(String str);
}
  • LeftFragment
private TextView mTextView;
    private MyFragmentListener mMyFragmentListener;

    @Override
    public void onAttach(@NonNull Context context) {
        mMyFragmentListener = (MyFragmentListener) context;
        super.onAttach(context);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_left,container,false);
        mTextView = view.findViewById(R.id.left_fragment_textView);
        checkBundle();
        mMyFragmentListener.whatFragmentSay("I am a left fragment");
        return view;
    }
  • MainActivity

static方法(Fragment--->Fragment)

  • RightFragment
/**
 * @return 返回了一个Fragment
 */
public Fragment changeFragment() {

    return RightFragmentTwo.newInstance("I am another right fragment");
}
  • RightFragmentTwo
public class RightFragmentTwo extends Fragment {

    private static final String KEY = "CHANGE_FRAGMENT_TO_MYSELF";

    private TextView mTextView;


    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_right_two, container, false);
        mTextView = view.findViewById(R.id.right_fragment_two_textView);
        checkBundle();

        return view;
    }

    /**
     * activity是否有传递数据
     */
    private void checkBundle() {
        Bundle bundle = this.getArguments();
        if (bundle != null) {
            String str = bundle.getString(KEY);
            mTextView.setText(str);
        }
    }

    /**
     * @param str 用来传入需要显示的字符串
     * @return 返回一个fragment
     */
    public static Fragment newInstance(String str) {
        Fragment fragment = new RightFragmentTwo();
        Bundle bundle = new Bundle();
        bundle.putString(KEY, str);
        fragment.setArguments(bundle);
        return fragment;
    }
}
  • MainActivity
@Override
public void changeRightFragment() {
    if (mAnotherRightFragment == null) {
        mAnotherRightFragment = ((RightFragment) mRightFragment).changeFragment();
        displayFragment(R.id.right_frame_layout, mAnotherRightFragment);
        isAnotherRightFragment = true;
        showBackStack();
    } else if (isRightFragment && !isAnotherRightFragment) {
        showHideFragment(mAnotherRightFragment, mRightFragment);
        isRightFragment = false;
        isAnotherRightFragment = true;
    } else {
        showHideFragment(mRightFragment, mAnotherRightFragment);
        isRightFragment = true;
        isAnotherRightFragment = false;
    }
}
  • 切换界面之前

切换界面之后

源码地址

https://github.com/qricis/DoSomeAndroidTest/tree/main/MyFragmentTransferTest

Fragment之设置最大生命周期setMaxLifecycle

何为setMaxLifecycle

  • 定义在FragmentTransaction中,与add()、attach()等方法并列
/**
 * Set a ceiling for the state of an active fragment in this FragmentManager. If fragment is
 * already above the received state, it will be forced down to the correct state.
 *
 * <p>The fragment provided must currently be added to the FragmentManager to have it's
 * Lifecycle state capped, or previously added as part of this transaction. The
 * {@link Lifecycle.State} passed in must at least be {@link Lifecycle.State#CREATED}, otherwise
 * an {@link IllegalArgumentException} will be thrown.</p>
 *
 * @param fragment the fragment to have it's state capped.
 * @param state the ceiling state for the fragment.
 * @return the same FragmentTransaction instance
 */
@NonNull
public FragmentTransaction setMaxLifecycle(@NonNull Fragment fragment,
                                           @NonNull Lifecycle.State state) {
    addOp(new Op(OP_SET_MAX_LIFECYCLE, fragment, state));
    return this;
}
  • 两个参数 fragment即要操作的对象    state生命周期状态

生命周期状态

  • DESTROYED
  • INITIALIZED
  • CREATED
  • STARTED
  • RESUMED

add()

private void checkMaxLifecycle() {
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.add(R.id.left_frame_layout,mLeftFragment);
    transaction.commitNowAllowingStateLoss();
}

add() + CREATED(onCreate())

private void checkMaxLifecycle() {
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.add(R.id.left_frame_layout,mLeftFragment);
    transaction.setMaxLifecycle(mLeftFragment, Lifecycle.State.CREATED);
    transaction.commitNowAllowingStateLoss();
}

add() + STARTED(onStart())

private void checkMaxLifecycle() {
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.add(R.id.left_frame_layout,mLeftFragment);
    transaction.setMaxLifecycle(mLeftFragment, Lifecycle.State.STARTED);
    transaction.commitNowAllowingStateLoss();
}

add() + RESUMED(onResume())

private void checkMaxLifecycle() {
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.add(R.id.left_frame_layout,mLeftFragment);
    transaction.setMaxLifecycle(mLeftFragment, Lifecycle.State.RESUMED);
    transaction.commitNowAllowingStateLoss();
}

RESUMED + CREATED(onDestroyView())

new Handler(Looper.myLooper()).postDelayed(() -> {
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.setMaxLifecycle(mLeftFragment, Lifecycle.State.CREATED);
    fragmentTransaction.commit();
}, 500);

RESUMED + STARTED(onPause())

new Handler(Looper.myLooper()).postDelayed(() -> {
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.setMaxLifecycle(mLeftFragment, Lifecycle.State.STARTED);
    fragmentTransaction.commit();
}, 500);

RESUMED + CREATED + STARTED(onStart())

new Handler(Looper.myLooper()).postDelayed(() -> {
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.setMaxLifecycle(mLeftFragment, Lifecycle.State.STARTED);
    fragmentTransaction.setMaxLifecycle(mLeftFragment, Lifecycle.State.CREATED);
    fragmentTransaction.commit();
}, 500);

注意

  • 传入的参数状态不得低于CREATED,否则会报错IllegalArgumentException

The fragment provided must currently be added to the FragmentManager to have it's Lifecycle state capped, or previously added as part of this transaction. The {@link  Lifecycle.State} passed in must at least be {@link  Lifecycle.State#CREATED}, otherwise an {@link  IllegalArgumentException} will be thrown .

new Handler(Looper.myLooper()).postDelayed(() -> {
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.setMaxLifecycle(mLeftFragment, Lifecycle.State.DESTROYED);
    fragmentTransaction.commit();
}, 500);

Fragment 的延迟加载

why?

  • 对于Fragment而言,我们通常会结合ViewPager使用,其会默认左右各预加载一个页面保证ViewPager的流畅性(ViewPager 中你可以设置预缓存的fragment的个数,使用setOffscreenPageLimit,默认为1)
  • 但是如果预加载的界面有一些比较耗时耗流量的操作,就会比较浪费资源

前世

add+show+hide模式(onHiddenChanged() + isHidden())

  • Fragment# onHiddenChanged(boolean hidden)
​
/**

Called when the hidden state (as returned by {@link  #isHidden()} of the fragment has changed.  Fragments start out not hidden; this will be called whenever the fragment changes state from that.

@param  hidden True if the fragment is now hidden, false otherwise.


*/

public void onHiddenChanged(boolean hidden) {
}

​

每当fragment状态发生变化时(是否隐藏),该方法会被调用

  • Fragment# isHidden()
​
/**

Return true if the fragment has been hidden.  

By default fragments are shown.  You can find out about changes to this state with {@link  #onHiddenChanged}.  

Note that the hidden state is orthogonal to other states -- that is, to be visible to the user, a fragment must be both started and not hidden.


*/

final public boolean isHidden() {
return mHidden;
}

​

能获取到该fragment的状态(是否隐藏)

  • 通过判断该fragment是否处于隐藏状态,来选择是否执行消耗资源的操作(但是隐藏的fagment还是处于onResume状态)

ViewPager + Fragment模式(setUserVisibleHint(isVisibleToUser: Boolean))

  • Fragmetn# setUserVisibleHint()
/**
 * Set a hint to the system about whether this fragment's UI is currently visible
 * to the user. This hint defaults to true and is persistent across fragment instance
 * state save and restore.
 *
 * <p>An app may set this to false to indicate that the fragment's UI is
 * scrolled out of visibility or is otherwise not directly visible to the user.
 * This may be used by the system to prioritize operations such as fragment lifecycle updates
 * or loader ordering behavior.</p>
 *
 * <p><strong>Note:</strong> This method may be called outside of the fragment lifecycle.
 * and thus has no ordering guarantees with regard to fragment lifecycle method calls.</p>
 *
 * @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
 *                        false if it is not.
 *
 * @deprecated Use {@link FragmentTransaction#setMaxLifecycle(Fragment, Lifecycle.State)}
 * instead.
 */
@Deprecated
public void setUserVisibleHint(boolean isVisibleToUser) {
    if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
            && mFragmentManager != null && isAdded() && mIsCreated) {
        mFragmentManager.performPendingDeferredStart(this);
    }
    mUserVisibleHint = isVisibleToUser;
    mDeferStart = mState < STARTED && !isVisibleToUser;
    if (mSavedFragmentState != null) {
        // Ensure that if the user visible hint is set before the Fragment has
        // restored its state that we don't lose the new value
        mSavedUserVisibleHint = isVisibleToUser;
    }
}
  • 用该方法来获取fragment的隐藏与显示状态,然后通过状态来判断是否执行耗时操作(但是如今该方法已经过时了,且隐藏的fragment还是处于onResume状态哦)

优缺点

  • 优点

        不用自己去控制add+show+hide,所有的“懒加载”都依靠setUserVisibleHint() + onHiddenChanged() 这两个方法

  • 缺点

        所有不可见的Fragment其实都处于onResume状态,依旧浪费资源

今生

setMaxLifecycle(Fragment fragment,LifeStyle.State state)

  • 由于Androidx中FragmentTransaction增加了setMaxLifecycle()方法,可以用来控制fragment的最大生命周期状态(CREATED/STARTED/RESUMED)
  • 详细的使用详见另一篇pdf

ViewPager + Fragment的懒加载实现

  • FragmentPagerAdapter/FragmentStatePagerAdapter中新增加了一个参数behavior,该参数有两个可取值
  • FragmentPagerAdapter/FragmentStatePagerAdapter
/**
 * Constructor for {@link FragmentPagerAdapter} that sets the fragment manager for the adapter.
 * This is the equivalent of calling {@link #FragmentPagerAdapter(FragmentManager, int)} and
 * passing in {@link #BEHAVIOR_SET_USER_VISIBLE_HINT}.
 *
 * <p>Fragments will have {@link Fragment#setUserVisibleHint(boolean)} called whenever the
 * current Fragment changes.</p>
 *
 * @param fm fragment manager that will interact with this adapter
 * @deprecated use {@link #FragmentPagerAdapter(FragmentManager, int)} with
 * {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}
 */
@Deprecated
public FragmentPagerAdapter(@NonNull FragmentManager fm) {
    this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT);
}

/**
 * Constructor for {@link FragmentPagerAdapter}.
 *
 * If {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} is passed in, then only the current
 * Fragment is in the {@link Lifecycle.State#RESUMED} state. All other fragments are capped at
 * {@link Lifecycle.State#STARTED}. If {@link #BEHAVIOR_SET_USER_VISIBLE_HINT} is passed, all
 * fragments are in the {@link Lifecycle.State#RESUMED} state and there will be callbacks to
 * {@link Fragment#setUserVisibleHint(boolean)}.
 *
 * @param fm fragment manager that will interact with this adapter
 * @param behavior determines if only current fragments are in a resumed state
 */
public FragmentPagerAdapter(@NonNull FragmentManager fm,
        @Behavior int behavior) {
    mFragmentManager = fm;
    mBehavior = behavior;
}

 behavior的取值

/**
 * Indicates that {@link Fragment#setUserVisibleHint(boolean)} will be called when the current
 * fragment changes.
 *
 * @deprecated This behavior relies on the deprecated
 * {@link Fragment#setUserVisibleHint(boolean)} API. Use
 * {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} to switch to its replacement,
 * {@link FragmentTransaction#setMaxLifecycle}.
 * @see #FragmentPagerAdapter(FragmentManager, int)
 */
@Deprecated
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0;

/**
 * Indicates that only the current fragment will be in the {@link Lifecycle.State#RESUMED}
 * state. All other Fragments are capped at {@link Lifecycle.State#STARTED}.
 *
 * @see #FragmentPagerAdapter(FragmentManager, int)
 */
public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;

 如果behavior的取值为BEHAVIOR_SET_USER_VISIBLE_HINT时,当 fragment 对用户的可见状态发生改变时,setUserVisibleHint()将会被调用(虽然还是会处于onResume状态)

  • 如果取值为BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT时,只有当前用户可见的fragment会处于onResume,其余的处于STARTED状态
  • setPeimaryItem
@SuppressWarnings({"ReferenceEquality", "deprecation"})
@Override
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
    Fragment fragment = (Fragment)object;
    if (fragment != mCurrentPrimaryItem) {
        if (mCurrentPrimaryItem != null) {
            mCurrentPrimaryItem.setMenuVisibility(false);
            if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
                if (mCurTransaction == null) {
                    mCurTransaction = mFragmentManager.beginTransaction();
                }
                mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
            } else {
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
        }
        fragment.setMenuVisibility(true);
        if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
            mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
        } else {
            fragment.setUserVisibleHint(true);
        }

        mCurrentPrimaryItem = fragment;
    }
}
  • 归根结底,还是该方法内调用了setMaxLifecycle,让hide的fragment处于STARTED状态,让即将显示的fragment处于RESUMED状态
  • MainActivity

  • MyFragmentPagerAdapter

  • 结果
  • 显示FragmentOne

 显示FragmentTwo

 显示FragmentThree

viewpager + Fragment的嵌套实现

  • 当fragment嵌套子fragment时,子fragment能否实现懒加载呢?答案是可以的
  • 显示界面一&子界面一

  • 显示界面一&子界面二

  • 显示界面一&子界面三

  • 显示界面二

  • 显示界面三

源码地址

DoSomeAndroidTest/MyViewPager at main · qricis/DoSomeAndroidTest · GitHub

add + show + hide 模式下的懒加载实现

  • 可以根据FragmentPagerAdapter/FragmentStatePagerAdapter中的setPeimaryItem方法的写法来实现懒加载
  • 具体实现方法可参照Androidx下Fragment懒加载的新实现
  • 简单来说,就是将需要显示的fragment执行add + show 之后,setMaxLifecycle(fragment,Lifecycle.State.RESUME)
  • 将需要隐藏的fragment执行hide后,setMaxLifecycle(fragment,Lifecycle.State.START)

优缺点

  • 优点
  • 只有当前的fragment处于onResume状态,其余的处于onStart或者onPause状态
  • 在嵌套状态下,子Fragment在隐藏状态下也能处于onStart状态
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值