目前网上主流的文章都是用底部的 RadioGroup 页面部分的 Fragment 实现导航栏切换页面效果的。

然而底部的 RadioGroup 是如此麻烦,每个按钮的图片和文字部分都要做一个 selector 用于表示选中和非选中两种状态时的样式。

另外 Fragment 也有很多坑,先不管大家是否已熟练掌握,反正我是看着看着就学不下去了,所以我另辟蹊径用 Activity 的方式实现了伪 Fragment 的效果。

这里我们就来做一个三个按钮的底部导航栏。

因为我们这里是用三个 Activity 实现三个页面,而并非一个 Activity 中的三个 Fragment,所以在此之前,我们需要建立一个管理活动堆栈的类,以便在程序退出时能直接结束堆栈中的所有活动,不至于要依次退出三个 Activity。

在 java 代码目录里新建一个 base 包,在包内新建文件 AppManager.java:

/**
 * AppManager: 用于对活动进行管理。
 * 该模块仅限 base 包内使用。
 * 该模块为单一实例,您需要调用 AppManager.get() 获取实例后再调用方法。
 * <p>
 * 为确保应用管理器正常工作,请新建一个继承 Activity 的抽象类 BaseActivity,
 * 然后重写 BaseActivity 类的 onCreate() 和 onDestroy() 方法。
 * 请给 BaseActivity 类的 onCreate() 方法添加如下代码:
 * AppManager.get().addActivity(this);
 * 请给 BaseActivity 类的 onDestroy() 方法添加如下代码:
 * AppManager.get().removeActivity(this);
 * 最后,确保本 APP 内的所有活动类均继承于 BaseActivity 类。
 */
class AppManager {
    private static AppManager sManager = new AppManager();
    private Stack<BaseActivity> mActivities;
 
    private AppManager() {
        // 将作用域关键字设置为 private 以隐藏该类的构造器。
        // 该类的单例由 get() 方法引用。
        // 创建单例的同时创建活动堆栈。
        mActivities = new Stack<>();
    } // AppManager() (Class Constructor)
 
    /**
     * get(): 获得 AppManager 类的单例。
     *
     * @return 该类的单例 sManager。
     */
    static AppManager get() {
        return sManager;
    } // get()
 
    /**
     * addActivity(): 向堆栈中添加一个活动对象。
     *
     * @param activity 要添加的活动对象。
     */
    void addActivity(BaseActivity activity) {
        mActivities.add(activity);
    } // addActivity()
 
    /**
     * removeActivity(): 从堆栈中移除一个活动对象。
     *
     * @param activity 要移除的活动对象。
     */
    void removeActivity(BaseActivity activity) {
        mActivities.remove(activity);
    } // removeActivity()
 
    /**
     * finishAllExcept(): 除一个特定活动外,结束堆栈中其余所有活动。
     * 结束活动时会触发 BaseActivity 类的 onDestroy()方法,
     * 堆栈中的活动对象会同步移除。
     *
     * @param activityClass 要保留的活动的类名(xxxActivity.class)
     */
    void finishAllExcept(Class activityClass) {
        int i, len;
        BaseActivity[] activities;
 
        // 结束活动时会调用活动的 onDestroy() 方法,堆栈的内容会实时改变
        // 为避免因此引起的引用错误,先将堆栈的内容复制到一个临时数组里
        activities = mActivities.toArray(new BaseActivity[0]);
        len = activities.length;
        for (i = 0; i < len;   i) {
            if (!activities[i].getClass().equals(activityClass)) {
                // 从数组里引用活动对象并结束,堆栈内容的改变不影响数组
                activities[i].finish();
            } // if (!activities[i].getClass().equals(activityClass))
        } // for (i = 0; i < len;   i)
    } // finishAllExcept()
 
    /**
     * finishAllActivities(): 结束堆栈中的所有活动。
     * 结束活动时会触发 BaseActivity 类的 onDestroy()方法,
     * 堆栈中的活动对象会同步移除。
     */
    void finishAllActivities() {
        int i, len;
        BaseActivity[] activities;
 
        // 结束活动时会调用活动的 onDestroy() 方法,堆栈的内容会实时改变
        // 为避免因此引起的引用错误,先将堆栈的内容复制到一个临时数组里
        activities = mActivities.toArray(new BaseActivity[0]);
        len = activities.length;
        for (i = 0; i < len;   i) {
            // 从数组里引用活动对象并结束,堆栈内容的改变不影响数组
            activities[i].finish();
        } // for (i = 0; i < len;   i)
    } // finishAllActivities()
} // AppManager Class
 
// E.O.F

上述代码粘贴完后会报错,别着急,那是因为我们还没有建立和管理器相关联的 BaseActivity 类。现在我们在 base 包内再新建一个 BaseActivity.java,封装活动间的跳转方法。其中 jumpTo() 方法表示跳转后活动堆栈中只保留跳转后的那一个活动,压在堆栈中的其他活动全部销毁;而 open() 方法则保留活动堆栈。另外还有一个 showExitDialog() 的方法,用于询问用户是否退出程序,当用户选择“是”时,将堆栈中的所有活动一次性全部销毁。

/**
 * BaseActivity: 该抽象类定义所有活动均拥有的共同属性。
 * 本 APP 中所有活动对象均继承此类。
 */
public abstract class BaseActivity extends Activity {
    /**
     * onCreate(): 重写父类的 onCreate() 方法,向应用管理器中添加本活动。
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AppManager.get().addActivity(this);
    } // onCreate()
 
    /**
     * onDestroy(): 重写父类的 onDestroy() 方法,从应用管理器中移除本活动。
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        AppManager.get().removeActivity(this);
    } // onDestroy()
 
    /**
     * jumpTo(): 实现不传参的活动间跳转。
     *
     * @param dst 要跳转到的活动的类名(xxxActivity.class)。
     */
    protected void jumpTo(Class dst) {
        Intent intent = new Intent(this, dst);
        startActivity(intent);
        AppManager.get().finishAllExcept(dst);
    } // jumpTo()
 
    /**
     * open(): 将当前活动压入堆栈,打开一个新活动。
     *
     * @param dst 要打开的活动的类名(xxxActivity.class)。
     */
    protected void open(Class dst) {
        Intent intent = new Intent(this, dst);
        startActivity(intent);
    } // open()
 
    /**
     * showExitDialog(): 显示退出程序对话框,询问用户是否退出程序。
     */
    protected void showExitDialog() {
        AlertDialog dialog;
        DialogInterface.OnClickListener onClick;
 
        onClick = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                switch (which) {
                    case DialogInterface.BUTTON_POSITIVE:
                        // 确定按钮
                        dialog.dismiss();
                        AppManager.get().finishAllActivities();
                        break; // case DialogInterface.BUTTON_POSITIVE
 
                    case DialogInterface.BUTTON_NEGATIVE:
                        // 取消按钮
                        dialog.dismiss();
                        break; // case DialogInterface.BUTTON_NEGATIVE
 
                    default:
                        break; // default
                } // switch (which)
            } // onClick()
        }; // onClick = new DialogInterface.OnClickListener()
 
        // 显示对话框
        dialog = new AlertDialog.Builder(this)
            .setMessage(R.string.dlgExitMsg) // "确定要退出吗?"
            .setPositiveButton(android.R.string.ok, onClick)
            .setNegativeButton(android.R.string.cancel, onClick)
            .create(); // dialog = new AlertDialog.Builder(this)...
        dialog.show();
    } // showExitDialog()
} // BaseActivity Abstract Class
 
// E.O.F

上述代码粘贴完后还是有一处报错。这是因为这段代码中用到了一处尚未定义的字符串资源。我们打开 res/values 目录下的 strings.xml 文件,添加字符串资源:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 这个字符串是你的工程名称,可以自己取,不一定要是 My Application -->
    <string name="app_name">My Application</string>
 
    <!-- 这个是我们新添加的字符串 -->
    <string name="dlgExitMsg">确定要退出吗?</string>
 
    <!-- 顺便把之后要用到的字符串也一并准备了 -->
    <string name="btnNavHome">主页</string>
    <string name="btnNavMessage">消息</string>
    <string name="btnNavSettings">设置</string>
</resources>

至此,报错全部消除。

现在我们建立底部导航栏的三个按钮对应的三个 Activity 页面。

准备导航栏的图标资源,放入 res/drawable 文件夹内:

打开 res/values/colors.xml 文件,定义导航栏相关颜色(背景、选中颜色、非选中颜色)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- 定义导航栏的相关颜色 -->
    <color name="navBack">#e0e0e0</color> <!-- 导航栏背景色 Grey 300-->
    <color name="navNormal">#000000</color> <!-- 未激活项目的文字颜色 Black -->
    <color name="navActivated">#039be5</color> <!-- 已激活项目的文字颜色 Light Blue 600 -->
</resources>

新建三个活动 HomeActivity、MessageActivity 和 SettingsActivity。

MainActivity 的布局文件 activity_main.xml 如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="@android:color/holo_blue_dark"
    tools:context=".HomeActivity">
 
    <LinearLayout
        android:id="@ id/llHomePage"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginBottom="8dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toTopOf="@ id/llHomeNav"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">
        <!--
        该 LinearLayout 为主体页面布局的容器,你也可以根据需要换成其他形式的 Layout
        以上代码将页面布局容器和底部的导航栏进行了约束
        即该容器的底端和导航栏的顶端彼此约束
        确保该容器的占用空间不会覆盖导航栏
        页面的主体布局请在该容器内部(即此处)创建
        -->
    </LinearLayout>
 
    <!-- 以下为导航栏的布局 -->
    <LinearLayout
        android:id="@ id/llHomeNav"
        style="?android:attr/buttonBarStyle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@color/navBack"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@ id/llHomePage">
 
        <!-- 【主页】活动中,【主页】按钮设为已激活样式,注意 drawableTop 和 textColor 属性的值 -->
        <Button
            android:id="@ id/btnNavHome"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:drawableTop="@drawable/home1"
            android:onClick="onNavButtonsTapped"
            android:text="@string/btnNavHome"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="@color/navActivated" />
 
        <!-- 其他按钮设为未激活样式,注意 drawableTop 和 textColor 属性的值 -->
        <Button
            android:id="@ id/btnNavMessage"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:drawableTop="@drawable/message0"
            android:onClick="onNavButtonsTapped"
            android:text="@string/btnNavMessage"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="@color/navNormal" />
 
        <Button
            android:id="@ id/btnNavSettings"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:drawableTop="@drawable/settings0"
            android:onClick="onNavButtonsTapped"
            android:text="@string/btnNavSettings"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="@color/navNormal" />
    </LinearLayout>
</android.support.constraint.ConstraintLayout>

切换到预览页面看一下,已经有了底部导航栏的雏形:

现在打开代码文件 HomeActivity.java 编写点击导航栏按钮时的活动跳转代码:

public class HomeActivity extends BaseActivity { // 请注意此处继承的是 BaseActivity 而不是 Activity
    /**
     * onCreate(): 活动创建时触发。
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
    } // onCreate()
 
    /**
     * onNavButtonsTapped(): 点击导航栏上的标签时触发。
     *
     * @param v 点击的按钮对象,用 v.getId() 获取其资源 ID。
     */
    public void onNavButtonsTapped(View v) {
        switch (v.getId()) {
            case R.id.btnNavMessage:
                open(MessageActivity.class);
                break; // case R.id.btnNavMessage
 
            case R.id.btnNavSettings:
                open(SettingsActivity.class);
                break; // case R.id.btnNavSettings
        } // switch (v.getId())
    } // onNavButtonsTapped()
 
    /**
     * onKeyDown(): 按下回退键时触发。
     * 弹出对话框询问是否退出程序。
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            showExitDialog();
            return true;
        } // if (keyCode == KeyEvent.KEYCODE_BACK)
        else {
            return super.onKeyDown(keyCode, event);
        } // else
    } // onKeyDown()
} // HomeActivity Class
 
// E.O.F

另外两个活动直接照葫芦画瓢就 OK 了。不过这里千万要注意后两个活动不能只复制 MainActivity 的布局就完事了,一定要更改导航栏各个按钮的样式!另外就是活动中的 onNavButtonsTapped() 方法的内容也不一样!

activity_message.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="@android:color/holo_green_dark"
    tools:context=".MessageActivity"> <!-- 本活动的背景色为 holo green dark -->
    <!-- 注意上方的 Context -->
 
    <LinearLayout
        android:id="@ id/llMessagePage"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginBottom="8dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toTopOf="@ id/llMessageNav"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">
        <!-- 在此定义页面主体布局 -->
    </LinearLayout>
 
    <LinearLayout
        android:id="@ id/llMessageNav"
        style="?android:attr/buttonBarStyle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@color/navBack"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@ id/llMessagePage">
 
        <!-- 请务必注意以下各按钮的 drawableTop 和 textColor 属性 -->
        <Button
            android:id="@ id/btnNavHome"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:drawableTop="@drawable/home0"
            android:onClick="onNavButtonsTapped"
            android:text="@string/btnNavHome"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="@color/navNormal" />
 
        <Button
            android:id="@ id/btnNavMessage"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:drawableTop="@drawable/message1"
            android:onClick="onNavButtonsTapped"
            android:text="@string/btnNavMessage"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="@color/navActivated" />
 
        <Button
            android:id="@ id/btnNavSettings"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:drawableTop="@drawable/settings0"
            android:onClick="onNavButtonsTapped"
            android:text="@string/btnNavSettings"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="@color/navNormal" />
    </LinearLayout>
</android.support.constraint.ConstraintLayout>

MessageActivity.java

public class MessageActivity extends BaseActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_message);
    } // onCreate()
 
    // 请注意本方法内容的变化
    public void onNavButtonsTapped(View v) {
        switch (v.getId()) {
            case R.id.btnNavHome:
                open(HomeActivity.class);
                break; // case R.id.btnNavHome
 
            case R.id.btnNavSettings:
                open(SettingsActivity.class);
                break; // case R.id.btnNavSettings
        } // switch (v.getId())
    } // onNavButtonsTapped()
 
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            showExitDialog();
            return true;
        } // if (keyCode == KeyEvent.KEYCODE_BACK)
        else {
            return super.onKeyDown(keyCode, event);
        } // else
    } // onKeyDown()
} // MessageActivity Class
 
// E.O.F

最后是 SettingsActivity。

activity_settings.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="@android:color/holo_orange_dark"
    tools:context=".SettingsActivity"> <!-- 本活动的背景色为 holo orange dark -->
    <!-- 注意上方的 Context -->
 
    <LinearLayout
        android:id="@ id/llHomePage"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginBottom="8dp"
        android:orientation="vertical"
        app:layout_constraintBottom_toTopOf="@ id/llHomeNav"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">
        <!-- 在此定义页面主体布局 -->
    </LinearLayout>
 
    <!-- 以下为导航栏的布局 -->
    <LinearLayout
        android:id="@ id/llHomeNav"
        style="?android:attr/buttonBarStyle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:background="@color/navBack"
        android:orientation="horizontal"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@ id/llHomePage">
 
        <!-- 请务必注意以下各按钮的 drawableTop 和 textColor 属性 -->
        <Button
            android:id="@ id/btnNavHome"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:drawableTop="@drawable/home0"
            android:onClick="onNavButtonsTapped"
            android:text="@string/btnNavHome"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="@color/navNormal" />
 
        <Button
            android:id="@ id/btnNavMessage"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:drawableTop="@drawable/message0"
            android:onClick="onNavButtonsTapped"
            android:text="@string/btnNavMessage"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="@color/navNormal" />
 
        <Button
            android:id="@ id/btnNavSettings"
            style="?android:attr/buttonBarButtonStyle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:drawableTop="@drawable/settings1"
            android:onClick="onNavButtonsTapped"
            android:text="@string/btnNavSettings"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="@color/navActivated" />
    </LinearLayout>
</android.support.constraint.ConstraintLayout>

SettingsActivity.java:

package com.example.myapplication;
 
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
 
import com.example.myapplication.base.BaseActivity;
 
public class SettingsActivity extends BaseActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);
    } // onCreate()
 
    // 请注意本方法内容的变化
    public void onNavButtonsTapped(View v) {
        switch (v.getId()) {
            case R.id.btnNavHome:
                open(HomeActivity.class);
                break; // case R.id.btnNavHome
 
            case R.id.btnNavMessage:
                open(MessageActivity.class);
                break; // case R.id.btnNavMessage
        } // switch (v.getId())
    } // onNavButtonsTapped()
 
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            showExitDialog();
            return true;
        } // if (keyCode == KeyEvent.KEYCODE_BACK)
        else {
            return super.onKeyDown(keyCode, event);
        } // else
    } // onKeyDown()
} // SettingsActivity Class
 
// E.O.F

做到这一步,先中场休息,打开模拟器调试一下。

我们之后还有两个问题需要解决:

①活动间的跳转是有动画的,而我们并不需要这画蛇添足的动画;

②这一点也是更重要的。每次一点击导航栏上的按钮,就会打开一个新活动。当我们从主页活动跳至设置活动,然后再由设置活动跳回主页活动时,系统的堆栈里其实是有两个主页活动的实例的,如果反复跳转,系统也会一直继续创建活动实例,原先的活动实例名存实亡,造成实质上的内存泄漏,直到最后内存不够用而崩溃。我们需要的是这样的效果:跳回曾经已经创建过的活动时,不要新建实例,而是直接重新引用原先活动的实例。这样,不论在导航栏上跳转多少次,内存中最多只会有 3 个活动,永远不会有内存泄漏的问题。

我们先来解决第一个问题。这个问题其实很好办,在 res/values 目录下有个 styles.xml 的文件,用于定义活动的主题样式。我们在其中增加几行代码用于关闭活动间的跳转动画:

<resources>
 
    <!-- Base application theme. -->
    <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <!-- 关闭动画 -->
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowAnimationStyle">@style/NoAnimation</item>
    </style>
 
    <!-- 定义无动画样式 -->
    <style name="NoAnimation">
        <item name="android:activityCloseEnterAnimation">@null</item>
        <item name="android:activityCloseExitAnimation">@null</item>
        <item name="android:activityOpenEnterAnimation">@null</item>
        <item name="android:activityOpenExitAnimation">@null</item>
        <item name="android:taskCloseEnterAnimation">@null</item>
        <item name="android:taskCloseExitAnimation">@null</item>
        <item name="android:taskOpenEnterAnimation">@null</item>
        <item name="android:taskOpenExitAnimation">@null</item>
        <item name="android:taskToBackEnterAnimation">@null</item>
        <item name="android:taskToBackExitAnimation">@null</item>
        <item name="android:taskToFrontEnterAnimation">@null</item>
        <item name="android:taskToFrontExitAnimation">@null</item>
    </style>
</resources>

接下来是第二个问题,保证每个活动只有唯一的实例,避免跳转过程中活动实例反复创建造成的内存泄漏。

Android 中有一种活动启动方式叫 singleInstance,它表示整个 Application 周期里对应的活动实例不能超过一个,当该活动已创建但之后又从其他的 intent 跳转而来时,不新建实例,而是引用已有的实例。另外,singleInstance 启动模式的活动各自拥有各自的活动堆栈,互不影响。我们打开 AndroidManifest,添加如下代码:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapplication">
 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".HomeActivity"
            android:launchMode="singleInstance"> <!-- 设置活动的启动方式为 singleInstance -->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".MessageActivity"
            android:launchMode="singleInstance" /> <!-- 如法炮制 -->
        <activity
            android:name=".SettingsActivity"
            android:launchMode="singleInstance" /> <!-- 如法炮制 -->
    </application>
 
</manifest>

再次打开模拟器进行调试:

效果 PERFECT。

至此,我们就实现了无 Fragment 的底部导航栏。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持Devmax。

Android实现底部导航栏效果的更多相关文章

  1. html5 canvas合成海报所遇问题及解决方案总结

    这篇文章主要介绍了html5 canvas合成海报所遇问题及解决方案总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Html5 video标签视频的最佳实践

    这篇文章主要介绍了Html5 video标签视频的最佳实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. HTML5在微信内置浏览器下右上角菜单的调整字体导致页面显示错乱的问题

    HTML5在微信内置浏览器下,在右上角菜单的调整字体导致页面显示错乱的问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

  4. ios – containerURLForSecurityApplicationGroupIdentifier:在iPhone和Watch模拟器上给出不同的结果

    我使用默认的XCode模板创建了一个WatchKit应用程序.我向iOSTarget,WatchkitAppTarget和WatchkitAppExtensionTarget添加了应用程序组权利.(这是应用程序组名称:group.com.lombax.fiveminutes)然后,我尝试使用iOSApp和WatchKitExtension访问共享文件夹URL:延期:iOS应用:但是,测试NSURL

  5. Ionic – Splash Screen适用于iOS,但不适用于Android

    我有一个离子应用程序,其中使用CLI命令离子资源生成的启动画面和图标iOS版本与正在渲染的启动画面完美配合,但在Android版本中,只有在加载应用程序时才会显示白屏.我检查了config.xml文件,所有路径看起来都是正确的,生成的图像出现在相应的文件夹中.(我使用了splash.psd模板来生成它们.我错过了什么?这是config.xml文件供参考,我觉得我在这里做错了–解决方法在config.xml中添加以下键:它对我有用!

  6. ios – 无法启动iPhone模拟器

    /Library/Developer/CoreSimulator/Devices/530A44CB-5978-4926-9E91-E9DBD5BFB105/data/Containers/Bundle/Application/07612A5C-659D-4C04-ACD3-D211D2830E17/ProductName.app/ProductName然后,如果您在Xcode构建设置中选择标准体系结构并再次构建和运行,则会产生以下结果:dyld:lazysymbolbindingFailed:Symbol

  7. Xamarin iOS图像在Grid内部重叠

    heyo,所以在Xamarin我有一个使用并在其中包含一对,所有这些都包含在内.这在Xamarin.Android中看起来完全没问题,但是在Xamarin.iOS中,图像与标签重叠.我不确定它的区别是什么–为什么它在Xamarin.Android中看起来不错但在iOS中它的全部都不稳定?

  8. 在iOS上向后播放HTML5视频

    我试图在iPad上反向播放HTML5视频.HTML5元素包括一个名为playbackRate的属性,它允许以更快或更慢的速率或相反的方式播放视频.根据Apple’sdocumentation,iOS不支持此属性.通过每秒多次设置currentTime属性,可以反复播放,而无需使用playbackRate.这种方法适用于桌面Safari,但似乎在iOS设备上的搜索限制为每秒1次更新–在我的情况下太慢了.有没有办法在iOS设备上向后播放HTML5视频?解决方法iOS6Safari现在支持playbackRat

  9. 使用 Swift 语言编写 Android 应用入门

    Swift标准库可以编译安卓armv7的内核,这使得可以在安卓移动设备上执行Swift语句代码。做梦,虽然Swift编译器可以胜任在安卓设备上编译Swift代码并运行。这需要的不仅仅是用Swift标准库编写一个APP,更多的是你需要一些框架来搭建你的应用用户界面,以上这些Swift标准库不能提供。简单来说,构建在安卓设备上使用的Swiftstdlib需要libiconv和libicu。通过命令行执行以下命令:gitclonegit@github.com:SwiftAndroid/libiconv-libi

  10. Android – 调用GONE然后VISIBLE使视图显示在错误的位置

    我有两个视图,A和B,视图A在视图B上方.当我以编程方式将视图A设置为GONE时,它将消失,并且它正下方的视图将转到视图A的位置.但是,当我再次将相同的视图设置为VISIBLE时,它会在视图B上显示.我不希望这样.我希望视图B回到原来的位置,这是我认为会发生的事情.我怎样才能做到这一点?编辑–代码}这里是XML:解决方法您可以尝试将两个视图放在RelativeLayout中并相对于彼此设置它们的位置.

随机推荐

  1. Flutter 网络请求框架封装详解

    这篇文章主要介绍了Flutter 网络请求框架封装详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. Android单选按钮RadioButton的使用详解

    今天小编就为大家分享一篇关于Android单选按钮RadioButton的使用详解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

  3. 解决android studio 打包发现generate signed apk 消失不见问题

    这篇文章主要介绍了解决android studio 打包发现generate signed apk 消失不见问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧

  4. Android 实现自定义圆形listview功能的实例代码

    这篇文章主要介绍了Android 实现自定义圆形listview功能的实例代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  5. 详解Android studio 动态fragment的用法

    这篇文章主要介绍了Android studio 动态fragment的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  6. Android用RecyclerView实现图标拖拽排序以及增删管理

    这篇文章主要介绍了Android用RecyclerView实现图标拖拽排序以及增删管理的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

  7. Android notifyDataSetChanged() 动态更新ListView案例详解

    这篇文章主要介绍了Android notifyDataSetChanged() 动态更新ListView案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下

  8. Android自定义View实现弹幕效果

    这篇文章主要为大家详细介绍了Android自定义View实现弹幕效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  9. Android自定义View实现跟随手指移动

    这篇文章主要为大家详细介绍了Android自定义View实现跟随手指移动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. Android实现多点触摸操作

    这篇文章主要介绍了Android实现多点触摸操作,实现图片的放大、缩小和旋转等处理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部