For the first time to read dry goods, you can pay attention to my public number: program ape cultivation center

Looking back on the initial learning of Android, I was working on a small project while learning. Due to the requirements of the project, I needed to realize a function of the bottom navigation bar. Due to the limited basic knowledge, I found a lot of blogs on Baidu, and roughly found two implementation schemes:

  • The first is to implement it directly with fragments (click toggle)

  • The second is the ViewPager+Fragment implementation (in addition to clicking to switch, it can also swipe left or right to switch).

Then I used the first method according to the needs, and the problem of Fragment overlapping occurred in the later period. Since this bug sometimes occurred, I didn’t know how to locate it (when I was a student), so I put it down for the time being. Now because of the learning progress (systematic learning Fragment), I have picked up this problem again. I want to write a blog about realizing functions and solving bugs. If there are any shortcomings, please leave a comment.

Implementation approach

When we enter the Activity, first display the first page, create the corresponding Fragment instance, and display it using add+show method. When we click on another page, call hide method to hide the displayed Fragment page (unavailable attribute set to unavailable). Then display the corresponding Fragment page (if it has been created, call show method directly; if it has not been created, call add+show method to display).


We can also use the replace method to switch pages, which is different from the hide+show method. Replace removes the fragment instance and then adds it again. This causes the fragment to go through its life cycle again each time it switches, creating a new instance and not saving the state of each fragment. Using the hide+show method simply makes the Fragment that is not displayed invisible and saves the state when it is displayed again.


Let’s start with the renderings

Now comes the code
  1. Start by creating an Activity and writing the page layout.
  • Start by creating a new Layout Xml File bottombar.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/colorBackGround"
    android:paddingTop="5dp"
    android:paddingBottom="5dp">

    <TextView
        android:id="@+id/text_clothes"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:drawableTop="@drawable/ic_clothes"
        android:text="@string/clothes"
        android:textSize="13sp"
        android:gravity="center_horizontal"/>

    <TextView
        android:id="@+id/text_food"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:drawableTop="@drawable/ic_food"
        android:text="@string/food"
        android:textSize="13sp"
        android:gravity="center_horizontal"/>

    <TextView
        android:id="@+id/text_hotel"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="wrap_content"
        android:drawableTop="@drawable/ic_hotel"
        android:text="@string/hotel"
        android:textSize="13sp"
        android:gravity="center_horizontal"/>

</LinearLayout>

Copy the code
  • Use the include tag reference in activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

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

    <View
        android:background="@color/colorGray"
        android:layout_width="match_parent"
        android:layout_height="0.1 dp"/>

    <include layout="@layout/bottombar"/>

</LinearLayout>

Copy the code
  1. With the layout in place, create three blank fragments (only one is listed here).
  • ClothesFragment.java
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.example.laughter.testdemo.R;

/**
 * A simple {@link Fragment} subclass.
 */
public class ClothesFragment extends Fragment {

    public ClothesFragment(a) {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_clothes, container, false); }}Copy the code
  • fragment_clothes.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".fragment.ClothesFragment">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/clothes"
        android:textSize="72sp"
        android:gravity="center"/>

</FrameLayout>

Copy the code

I added a TextView to each Fragment page to separate the Fragment pages. I just wrote the code in the Fragment according to my own needs.

  1. The next step is to write the logical control Fragment switch in MainActivity.
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

import com.example.laughter.testdemo.fragment.ClothesFragment;
import com.example.laughter.testdemo.fragment.FoodFragment;
import com.example.laughter.testdemo.fragment.HotelFragment;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    // The bottom menu bar has 3 textViews
    private TextView mTextClothes;
    private TextView mTextFood;
    private TextView mTextHotel;

    / / three fragments
    private Fragment mClothesFragment;
    private Fragment mFoodFragment;
    private Fragment mHotelFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        / / initialization
        init();
        // Set the default display of the first Fragment
        setFragment(0);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            default:
                break;
            case R.id.text_clothes:
                setFragment(0);
                break;
            case R.id.text_food:
                setFragment(1);
                break;
            case R.id.text_hotel:
                setFragment(2);
                break; }}private void init(a){
        // Initialize the controller
        mTextClothes = (TextView)findViewById(R.id.text_clothes);
        mTextFood = (TextView)findViewById(R.id.text_food);
        mTextHotel = (TextView)findViewById(R.id.text_hotel);

        // Set the listener
        mTextClothes.setOnClickListener(this);
        mTextFood.setOnClickListener(this);
        mTextHotel.setOnClickListener(this);
    }

    private void setFragment(int index){
        // Get the Fragment manager
        FragmentManager mFragmentManager = getSupportFragmentManager();
        // Start the transaction
        FragmentTransaction mTransaction = mFragmentManager.beginTransaction();
        // Hide all fragments
        hideFragments(mTransaction);
        switch (index){
            default:
                break;
            case 0:
                // Set the menu bar to selected state (modify text and image color)
                mTextClothes.setTextColor(getResources()
                        .getColor(R.color.colorTextPressed));
                mTextClothes.setCompoundDrawablesWithIntrinsicBounds(0,
                        R.drawable.ic_clothes_pressed,0.0);
                // Displays the corresponding Fragment
                if(mClothesFragment == null){
                    mClothesFragment = new ClothesFragment();
                    mTransaction.add(R.id.container, mClothesFragment,
                            "clothes_fragment");
                }else {
                    mTransaction.show(mClothesFragment);
                }
                break;
            case 1:
                mTextFood.setTextColor(getResources()
                        .getColor(R.color.colorTextPressed));
                mTextFood.setCompoundDrawablesWithIntrinsicBounds(0,
                        R.drawable.ic_food_pressed,0.0);
                if(mFoodFragment == null){
                    mFoodFragment = new FoodFragment();
                    mTransaction.add(R.id.container, mFoodFragment,
                            "food_fragment");
                }else {
                    mTransaction.show(mFoodFragment);
                }
                break;
            case 2:
                mTextHotel.setTextColor(getResources()
                        .getColor(R.color.colorTextPressed));
                mTextHotel.setCompoundDrawablesWithIntrinsicBounds(0,
                        R.drawable.ic_hotel_pressed,0.0);
                if(mHotelFragment == null){
                    mHotelFragment = new HotelFragment();
                    mTransaction.add(R.id.container, mHotelFragment,
                            "hotel_fragment");
                }else {
                    mTransaction.show(mHotelFragment);
                }
                break;
        }
        // Commit the transaction
        mTransaction.commit();
    }

    private void hideFragments(FragmentTransaction transaction){
        if(mClothesFragment ! =null) {/ / hide fragments
            transaction.hide(mClothesFragment);
            // Set the corresponding menu bar to the default state
            mTextClothes.setTextColor(getResources()
                    .getColor(R.color.colorText));
            mTextClothes.setCompoundDrawablesWithIntrinsicBounds(0,
                    R.drawable.ic_clothes,0.0);
        }
        if(mFoodFragment ! =null){
            transaction.hide(mFoodFragment);
            mTextFood.setTextColor(getResources()
                    .getColor(R.color.colorText));
            mTextFood.setCompoundDrawablesWithIntrinsicBounds(0,
                    R.drawable.ic_food,0.0);
        }
        if(mHotelFragment ! =null){
            transaction.hide(mHotelFragment);
            mTextHotel.setTextColor(getResources()
                    .getColor(R.color.colorText));
            mTextHotel.setCompoundDrawablesWithIntrinsicBounds(0,
                    R.drawable.ic_hotel,0.0); }}}Copy the code

The above code logic is very clear, according to the notes can see the basic, specific use of some controls by Baidu. At this point, the function has been implemented, but there will be a bug of overlapping fragments. The specific situation is as follows:

  1. What are the reasons for this? If you understand the Activity life cycle, you know that by default, when we rotate the screen, the Activity destroys and rebuilds, and this process is an exception to the life cycle, The onSaveInstanceState and onRestoreInstanceState methods are called to save and restore the state of the Activity, and the Fragment is included in the restored content. However, in the previous Activity we created instances of fragments and added them to FragmentTransaction. These instances do not remove when the Activity is rebuilt, but after the Activity is rebuilt, there is no object pointing to them. In the rebuilt Activity, the three Fragmeng objects we created point to NULL.

Therefore, in the rebuilt Activity, the Fragment instance will be created again and displayed, and the Fragment that was restored by the system will also be displayed in the previous state, which leads to multiple fragments overlapping. Of course, this bug can occur in any situation that causes an Activity to be destroyed and rebuilt, such as an Activity being killed in the background because of insufficient memory resources. Now that we know the reason, it is not difficult to solve it. The solution I came up with was to recreate the Fragment. Since the saved state will be restored, we should not allow the Fragment to recreate while the Activity is being rebuilt. So how do we do that? Again, you need to be familiar with the Activity lifecycle.

  • First, we need to add a judgment in the onCreate method that displays the default Fragment only when the Activity does not terminate and resume abnormally (i.e. SavedInstanceState == NULL).
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        / / initialization
        init();
        // Determine whether the Activity starts normally or is destroyed and rebuilt based on the incoming Bundle object
        if(savedInstanceState == null) {// Set the first Fragment to default
            setFragment(0); }}Copy the code
  • We then grab the original created Fragmen instances and point them to the Fragment object in the reconstructed Activity. And set a variable to save which Fragment is displayed before destroying and rebuilding. These two operations require overriding the onSaveInstanceState and onRestoreInstanceState methods. (Only the modified code is posted here)
public class MainActivity extends AppCompatActivity implements View.OnClickListener{...// Marks the Fragment currently displayed
    private int fragmentId = 0; .@Override
    protected void onSaveInstanceState(Bundle outState) {
        // Use the onSaveInstanceState method to save the currently displayed fragment
        outState.putInt("fragment_id",fragmentId);
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        FragmentManager mFragmentManager = getSupportFragmentManager();
        // Use FragmentManager to get the Fragment instance stored in FragmentTransaction
        mClothesFragment = (ClothesFragment)mFragmentManager
                .findFragmentByTag("clothes_fragment");
        mFoodFragment = (FoodFragment)mFragmentManager
                .findFragmentByTag("food_fragment");
        mHotelFragment = (HotelFragment)mFragmentManager
                .findFragmentByTag("hotel_fragment");
        // Restore the Fragment displayed before destruction
        setFragment(savedInstanceState.getInt("fragment_id")); }...Copy the code
  • After the modification, we can print the Fragment object to see if it points to the same instance after the reconstruction as before.

Obviously, the instance the Fragment object points to after being destroyed is the same as before. So our BottomBar is done!

To see the source code click here

Because the level is limited, have wrong place unavoidable, rather misdirect others, welcome big guy to point out! Code word is not easy, thank you for your attention!

🙏 If you are studying with me, you can follow my official account — ❤️ Program ape Development Center ❤️. Every week, we will share the technology of Android regularly.