安卓学习日志 Day10 — 重构 Miwok应用

概述

昨天已经尝试了名叫 ViewPager 的新技术,它采用 FragmentPagerAdapterFragments 的方式实现,用户可以通过左右滑动 或是在标题栏中点击来切换不同的页面(碎片 Fragment)。

那么,为了确保没有破坏应用任何功能,接下来 我会一步一步(分阶段地)将这项新技术应用到 Miwok 应用当中,尽可能多次在设备上运行下应用。

目标

  • 重构之前
  • 重构类别 Activity
  • 使用 ViewPager
  • 添加标签页

实现步骤

重构之前

在开始重构 Miwok 之前最好先将 所有 java 代码中 硬编码的字符串都写到 strings.xml 资源文件当中。

在这里插入图片描述

这样做有一个很大的好处是(本地化),当我们 需要考虑以其他语言作为母语的用户时(如,西班牙语),只需新建一个西班牙语言版的 字符串资源文件即可,不必去修改现有的默认版本(这里指英文版)。

意外事故

当我将所有的硬编码字符串写到 strings.xml 文件当中后,首先要做的是 运行应用,不幸的是 构建失败,提示 Execution failed for task ':app:mergeExtDexDebug'. (当时忘记截图了)。

自己折腾了一会,还是没有解决问题。

尝试 1:

报错信息中还含有 unix 之类的字眼,于是我猜想可能是系统问题,因为我是在 Manjaro Linux 当中运行的。(是的,今天还花了点时间重装了我的 Windwos 10 + Manjaro 双系统,随便在 Manjaro 上搭建了 安卓开发环境)

于是,我又切换到 Windwos 系统当中运行。。。。。好吧,还是同样的报错信息。这样就排除系统有问题的猜想了。

尝试 2:

以为有缓存之类的东西在作祟,就清理了一下项目再运行,,还是。。没有用。

解决方法:

在我琢磨了好久后,还是问问搜索引擎来的快。

在 Module 的 build.gradle 中添加这些内容:

android {
    
    
    ...
    defaultConfig {
    
    
        ...
        multiDexEnabled true // Add this line
    }
}

不清楚可以看这张图:

在这里插入图片描述

加入这行代码之后,确实能够正常运行了,这时 我的好奇心又上来了。把刚刚加入的这行代码去掉,还是能够 正常运行。。。??????满脸疑惑,不过好在能够正常运行了。(相关链接都放在了文末参考链接中,有兴趣就去看看。)

到这里 就基本都 OK 了,下一阶段将正式开始重构 Miwok 应用。

重构类别 Activity

**重要提示:**现在开始,因为要大幅改动代码,因此一定要保存下应用当前状态的副本。这样的话,至少有个可运转的应用作为恢复的备份版本。(使用版本控制工具也是很好的选择)

在这里插入图片描述

对各类别的单词列表页的重构暂时先不使用 ViewPager。先准备 Fragment。目前,我们有 4 个类别 Activity 和 0 个 Fragment。

在这里插入图片描述

在此阶段结束时,我们希望有 4 个 类别Activity 和 4 个 Fragment。每个 Activity 中包含一个 Fragment。

在这里插入图片描述

为了将代码逻辑移到 Fragment java 文件中,将复制/粘贴大量代码。代码基本上是一样 的。但是,任何假设位于 Activity 类中的代码都需要稍加修改,考虑到现在代码位于 Fragment 类中。例如,当位于 Fragment 类中时,Activity 生命周期调用(onCreate、 onStop)将不存在。但是会有其他类似的方法(onCreateView、onStop),因此,需要 根据 Fragment 生命周期调用稍加修改。

同样的 ,我讲给出 从具有 NumbersActivity(之前 的状态)转变为具有其中包含 NumbersFragment 的 NumbersActivity(之后的状态)的参考,另外几个页面也是一样的方法。

1)创建 NumbersFragment 类

首先,为 NumbersFragment 创建一个新的 Java 文件。右键点击 com.example.miwok 文件夹。依次转到 “新建” > “Fragment” > “Fragment (Blank)”。

在这里插入图片描述

然后按照向导填写相关内容。为 Fragment 取一个名字:NumbersFragment

Android Studio 将在 NumbersFragment.java 文件中自动为新建一个 Fragment 类。将 NumbersFragment.java 中自动生成的成员变量,以及和这些成员变量相关的代码删除,最后如下:

public class NumbersFragment extends Fragment {
    
    

    public NumbersFragment() {
    
    
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
    
    
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_numbers, container, false);
    }
}

2)复制 NumbersActivity 中的代码并粘贴到 NumbersFragment 中

首先将 NumbersActivity 中的全局变量复制到 NumbersFragment 中(与此 同时,从 NumbersActivity 中删除这些变量。)

在这里插入图片描述

将 NumbersActivity 中的 releaseMediaPlayer() 辅助方法复制到 NumbersFragment 中。(与此 同时,从 NumbersActivity 中删除。)

在这里插入图片描述

3)根据 Fragment 生命周期(而不是 Activity 生命周期)调整下代码。

重写该 Fragment 的 onStop() 方法。

按下键盘快捷键 Ctl + O 弹出一个 对话框,并选择要重写的方法。输入“onStop”,找到该结果后,点击“确定”。修改 onStop() 方法,使其调用 releaseMediaPlayer 方法:

在这里插入图片描述

重写该 Fragment 的 onCreateView() 方法。

Activity 的 onCreate() 方法与 Fragment 的 onCreateView() 方法稍微不同 在 Activity 的 onCreate() 方法中,我们可以调用 setContentView() 来为 该 Activity 设置布局。在 Fragment 中,我们需要根据 XML 布局资源 ID 获得 视图,并在 onCreateView() 方法中返回该视图。注意,Fragment 的布局将使用 word_list XML 布局资源,因为它将显示一个单词列表。

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                    Bundle savedInstanceState) {
    
    
   View rootView = inflater.inflate(R.layout.word_list, container, false);

   /** TODO: Insert all the code from the NumberActivity’s onCreate() method after the setContentView method call */

   return rootView;
}

然后将 NumbersActivity 的 onCreate() 方法中的其余代码全部移动到 NumbersFragment 的 onCreateView() 方法当中。

4)错误修复

复制了 NumbersActivity 的 onCreate() 方法中的代码后, Android Studio 中将会提示存在各种错误,因为代码认为是在 Activity 类中运行的,而不是 Fragment 类。下面是 关于如何解决每个错误的说明。

第 1 个错误: 你将遇到一个错误,提示无法解析“findViewById(int)”方法,因为 Fragment 没有 findViewById 方法,而 Activity 的确具有该方法。 ListView listView = (ListView) findViewById(R.id.list);

解决方法:对 rootView 对象调用 findViewById(int),其中应该包含 子视图,例如 ListView。 ListView listView = (ListView) rootView.findViewById(R.id.list);

第 2 个错误: 你将遇到一个错误,提示无法解析“getSystemService(String)”方法, 因为 Fragment 无法访问系统服务,而 Activity 可以。 mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

解决方法:首先获取 Activity 对象实例。这是封装当前 Fragment 的 Activity, 即 NumbersActivity 封装了 NumbersFragment。然后对该 Activity 对象调用 getSystemService(String)。

mAudioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);

第 3 个错误: 传入 WordAdapter 构造函数的参数存在问题,因为第一个参数“this” 指的是这个类(即 NumbersFragment),而 Fragment 不是有效的 Context。但是, 当“this”指的是 NumbersActivity 时代码是可行的,因为 Activity 是个有效的 Context。 WordAdapter adapter = new WordAdapter(this, words, R.color.category_numbers);

解决方法:传入封装此 Fragment 的 Activity 的引用并作为 context。 WordAdapter adapter = new WordAdapter(getActivity(), words, R.color.category_numbers);

第 4 个错误: 在创建 MediaPlayer 对象时,我们需要传入 context。同样的, “this”指的是 NumbersFragment(而不是 NumbersActivity),但是 Fragment 不是有效的 Context。 mMediaPlayer = MediaPlayer.create(NumbersActivity.this, word.getAudioResourceId());

解决方法:对第一个输入参数传入相关 Activity。 mMediaPlayer = MediaPlayer.create(getActivity(), word.getAudioResourceId());

在解决了这 4 个错误后,文件中应该没有任何错误了!

5)更新 NumbersActivity

在 res/layout 目录下,创建一个新的布局文件,叫做 activity_category.xml。 重要的是该视图具有一个 ID。我们选择为该视图提供一个 ID,叫做“container”。

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

现在,需要更新 NumbersActivity 来使用 NumbersFragment, 否则的话,两个类中将有重复的代码并执行相同的操作。

将 NumbersActivity 代码替换为下面的整段代码。我们将使用这个简化的NumbersActivity 来将 activity_category XML 布局资源设置为内容视图。然后创建一个新的 NumbersFragment,并使用 FragmentTransaction 将其插入 container 视图中 (暂时不需要了解其中的详情)。因为该 container 具有“match_parent”宽度和高度, 因此 NumbersFragment 将占据屏幕的整个宽度和高度。

package com.example.miwok;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class NumbersActivity extends AppCompatActivity {
    
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_category);
        getSupportFragmentManager().beginTransaction()
                .replace(R.id.container, new NumbersFragment())
                .commit();
    }

}

解释下,之前,NumbersActivity 用来展示 word_list.xml 布局。 现在,NumbersActivity 用来展示 activity_category.xml 布局, 而 NumbersFragment 用来展示 word_list.xml 布局。

现在,NumbersActivity 使用了 NumbersFragment 了!运行下应用,确保 Numbers 列表依然能运转。外观应该是一样的,因为正如之前解释的,这只是朝着目标前进的其中一个中间点。

接下来请对其他类别重复 1 到 5 的相同步骤。所有类别 都可以使用 activity_category.xml 布局资源。

最终,Miwok应用应该看起来是一样的,但是每个类别 Activity 将包含不同的 Fragment。 测试下应用,确保正确的 Activity 显示了正确的 Fragment。每个 Fragment 都具有相同的背景颜色。同时确保 音频播放功能依然能工作。

为了能够验证每个 Activity 当中的 Fragment 都能正常工作,我在每个 Fragment 的 onCreateView() 方法中 加入了一行 代码,使其加载时出现一个消息框:

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
    
    
        // Inflate the layout for this fragment
        View rootView = inflater.inflate(R.layout.word_list, container, false);

        Toast.makeText(getActivity(), "NumbersFragment createView.", Toast.LENGTH_SHORT).show();

        /** TODO: Insert all the code from the NumberActivity’s onCreate() method after the setContentView method call */
        ………………
    }

最后运行效果应该如下:

在这里插入图片描述

使用 ViewPager

现在,显示单词列表的逻辑代码已经位于 Fragment 中,可以 在 MainActivity 中使用 ViewPager 了。

当你完成后,应用应该是这样的。当应用打开时,立即就能看到 Numbers 单词列表。然后, 你可以在单词列表之间水平滑动。在后台,有一个 Activity (MainActivity),其中包含 一个 ViewPager(具有 4 个不同的 Fragment),以此可以判断出有一个 Activity,是因为 当在屏幕之间滑动时,应用栏中仅仅显示“Miwok”字样即可。

要在 Miwok 应用中使用 ViewPager 和 FragmentPagerAdapter,你需要作出以下更改。

1) 首先修改 activity_main.xml 布局以包含 ViewPager。可以删除此布局文件中之前具有的 4 个类别 TextView。

<?xml version="1.0" encoding="utf-8"?>
<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:background="@color/tan_background"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

2) 要在 ViewPager 中填充页面,我们需要一个适配器。

为适配器创建一个新文件,方法是:在“项目 (Project)”目录面板上右击 com.example.miwok 文件夹,然后依次转到“New” > “Java Class”。 创建一个新类,叫做 CategoryAdapter。

Android Studio 将自动在 CategoryAdapter.java 文件中 创建一个新的 Java 类,其中包含以下内容:

package com.example.miwok;

public class CategoryAdapter {
    
    
}

3) 用我们希望在 Miwok 应用中实现的逻辑重写各个方法。我们需要思考下:

问题: ViewPager 中需要有多少个页面? 答案: 4 个页面,所以我们应该在 CategoryAdapter getCount() 方法中返回 4 个。

问题: ViewPager 中的 4个页面如何存储?答案:使用 ArrayList 顺序容器

问题: 如果位置是 0,我们应该显示哪个 Fragment?位置是 1 或 2 呢? 答案: 在 CategoryAdapter getItem(int position) 方法中,返回ArrayList 中的第 position 索引位置的 Fragment对象。

最终的 CategoryAdapter 类应该如下所示:

package com.example.miwok;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;

import java.util.ArrayList;

/**
 * {@link CategoryAdapter} is a {@link FragmentPagerAdapter} that can provide the layout for
 * each list item based on a data source which is a list of {@link Word} objects.
 */
public class CategoryAdapter extends FragmentPagerAdapter {
    
    

    /**
     * All fragments that will be show in ViewPager.
     */
    ArrayList<Fragment> fragments;

    /**
     * Create a new {@link CategoryAdapter} object.
     *
     * @param fm is the fragment manager that will keep each fragment's state in the adapter
     *           across swipes.
     */
    public CategoryAdapter(@NonNull FragmentManager fm) {
    
    
        super(fm);
    }

    private void addFragment(Fragment fragment) {
    
    
        fragments.add(fragment);
    }

    @NonNull
    @Override
    public Fragment getItem(int position) {
    
    
        return fragments.get(position);
    }

    @Override
    public int getCount() {
    
    
        return fragments.size();
    }
}

4) 然后在 MainActivity 中,可以使用 CategoryAdapter 来为 ViewPager 提供支持。删除与 4 个类别 TextView 相关的所有旧代码。

只需找到在 XML 布局中声明的 ViewPager。然后创建一个新的 CategoryAdapter, 并使用 setAdapter 方法将该适配器设置到 ViewPager 上。

再定义一个 setupViewPager() 辅助函数,用于初始化 ViewPager 的数据源。

package com.example.miwok;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Adapter;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    
    

    private ViewPager viewPager;
    private CategoryAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);

        // Set the content of the activity to use the activity_main.xml layout file
        setContentView(R.layout.activity_main);

        // Find the view pager that will allow the user to swipe between fragments
        viewPager = (ViewPager) findViewById(R.id.viewpager);

        // Create an adapter that knows which fragment should be shown on each page
        adapter = new CategoryAdapter(getSupportFragmentManager());

        // Initialize all Fragments
        setupViewPager(adapter);

        // Set the adapter onto the view pager
        viewPager.setAdapter(adapter);
    }

    /**
     * Initialize all Fragments used for display in the ViewPager.
     *
     * @param adapter is used to manager fragments and viewPager.
     */
    private void setupViewPager(CategoryAdapter adapter) {
    
    
        adapter.addFragment(new NumbersFragment());
        adapter.addFragment(new FamilyFragment());
        adapter.addFragment(new ColorsFragment());
        adapter.addFragment(new PhrasesFragment());
    }

}

5) 测试代码,确保所有功能都能正常运行。

我遇到了一个异常,当我运行应用后 Miwok应用闪了一下就退出了,原因是 CategoryAdapter 中作为数据来源的成员变量 ArrayList<Fragment> 对象 fragments 没有被初始化。

由于忘记了初始化,所以 ViewPager 没有数据来源,页面也就无法加载,故而闪退。

    /**
     * All fragments that will be show in ViewPager.
     */
    ArrayList<Fragment> fragments = new ArrayList<Fragment>();

6) 如果代码能按预期得运行,则删除以下不必要的文件:

  • NumbersActivity.java
  • FamilyActivity.java
  • ColorsActivity.java
  • PhrasesActivity.java
  • activity_category.xml 布局文件

同时删除 AndroidManifest.xml 文件中的 Activity 声明。

7) 删除所有这些内容后,再次测试下应用,确保一切都正常运转,没有删除任何重要的内容。 并且确保音频播放功能依然能工作。

gif 动图超过允许上传大小,改天再弄它。

添加标签页

现在我们添加一些标签页,这样用户就知道可以滑动打开更多的页面。

首先,我们需要让标签页显示在屏幕上。然后我们需要根据红色标示改善外观。

1) 添加 TabLayout 视图

因为 Material Library 已经在项目中关联为依赖项(创建项目时自动添加的),所以可以直接使用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bdPgFqf7-1611020110112)(./Day10~2021-01-18.assets/image-20210119160338249.png)]

修改 MainActivity 布局,使其包含 ViewPager 和 TabLayout。 activity_main.xml 布局文件应该是这样的。

注意,这里为 TabLayout 分配了视图 ID,因为需要在 Java 代码中引用该视图。

<?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"
    android:background="@color/primary_color"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        style="@style/CategoryTab"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabGravity="fill"
        app:tabMode="fixed" />

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

2) 更改 CategoryAdapter ,使用 ArrayList 存储所有标签页的名称(字符串类型)

添加一个成员变量 用于存储 所有标签页的名称。

     /**
     * Titles of each fragment.
     */
    ArrayList<String> titlles = new ArrayList<String>();

现在我们需要告诉应用在每个标签页中显示什么文本。转到 CategoryAdapter.java 文件并重写 getPageTitle() 方法。 该方法本身是在超类(FragmentPagerAdapter)中定义的,但是 我们想要重写该方法,以便自定义标签页文本(即代码中的页面标题)。

快捷键 Ctrl + O ,然后 找到 getPageTitle() 方法,并更改如下(从标题数据源 titles 获得对应位置的标签页名称):

    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
    
    
        return titlles.get(position);
    }

更改 addFragment() 方法接受一个片段 Fragment 和 其名称。

    /**
     * Add a fragment and it's name to data list.
     *
     * @param fragment is a pager (item) to show in viewpager
     * @param title    is a name of the fragment
     */
    void addFragment(Fragment fragment, String title) {
    
    
        fragments.add(fragment);
        titlles.add(title);
    }

3) 修改 MainActivity 中辅助方法 setupViewPager() ,为每个片段加入其名称(也可以叫做标题)。

    /**
     * Initialize all Fragments used for display in the ViewPager.
     *
     * @param adapter is used to manager fragments and viewPager.
     */
    private void setupViewPager(CategoryAdapter adapter) {
    
    
        adapter.addFragment(new NumbersFragment(), getString(R.string.category_numbers));
        adapter.addFragment(new FamilyFragment(), getString(R.string.category_family));
        adapter.addFragment(new ColorsFragment(), getString(R.string.category_colors));
        adapter.addFragment(new PhrasesFragment(), getString(R.string.category_phrases));
    }

Family 的字符串资源可能有点太长了,可以到 res/values/strings.xml 资源文件将 category_family 的值改为 Famyliy

    <string name="category_family">Family</string>

4) 修改 MainActivity onCreate() 方法,使 TabLayout 与 ViewPager 相关联。

代码应该如下所示:

package com.example.miwok;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Adapter;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.material.tabs.TabLayout;

public class MainActivity extends AppCompatActivity {
    
    

    private ViewPager viewPager;
    private CategoryAdapter adapter;
    private TabLayout tabLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);

        // Set the content of the activity to use the activity_main.xml layout file
        setContentView(R.layout.activity_main);

        // Find the view pager that will allow the user to swipe between fragments
        viewPager = (ViewPager) findViewById(R.id.viewpager);

        // Create an adapter that knows which fragment should be shown on each page
        adapter = new CategoryAdapter(getSupportFragmentManager());

        // Initialize all Fragments
        setupViewPager(adapter);

        // Set the adapter onto the view pager
        viewPager.setAdapter(adapter);


        // The Page (fragment) titles will be displayed in the
        // tabLayout hence we need to set the page viewer
        // we use the setupWithViewPager().
        tabLayout = findViewById(R.id.tab_layout);
        tabLayout.setupWithViewPager(viewPager);
    }

    /**
     * Initialize all Fragments used for display in the ViewPager.
     *
     * @param adapter is used to manager fragments and viewPager.
     */
    private void setupViewPager(CategoryAdapter adapter) {
    
    
        adapter.addFragment(new NumbersFragment(), getString(R.string.category_numbers));
        adapter.addFragment(new FamilyFragment(), getString(R.string.category_family));
        adapter.addFragment(new ColorsFragment(), getString(R.string.category_colors));
        adapter.addFragment(new PhrasesFragment(), getString(R.string.category_phrases));
    }

}

5) 更改页面样式(背景色)

styles.xml 资源文件中添加样式,应用栏下面不应该出现 黑影。

    <!-- App bar style -->
    <style name="MiwokAppBarStyle" parent="style/Widget.AppCompat.Light.ActionBar.Solid.Inverse">
        <!-- Remove the shadow below the app bar -->
        <item name="elevation">0dp</item>
    </style>
	
	<!-- App bar style -->
	<style name="Theme.Miwok.ActionBar" parent="@style/Widget.AppCompat.Light.ActionBar.Solid.Inverse">
    	<!-- remove shadow below action bar -->
    	<!-- <item name="android:elevation">0dp</item> -->
    	<!-- Support library compatibility -->
    	<item name="elevation">0dp</item>
	</style>

    <!-- Style for a tab that displays a category name -->
    <style name="CategoryTab" parent="Widget.Design.TabLayout">
        <item name="tabIndicatorColor">@android:color/white</item>
        <item name="tabSelectedTextColor">@android:color/white</item>
        <item name="tabTextAppearance">@style/CategoryTabTextAppearance</item>
    </style>

    <!-- Text appearance style for a category tab -->
    <style name="CategoryTabTextAppearance" parent="TextAppearance.Design.Tab">
        <item name="android:textColor">#A8A19E</item>
    </style>

更改 themes.xml 主题资源文件

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.Miwok" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/primary_color</item>
        <item name="colorPrimaryDark">@color/primary_dark_color</item>
        <item name="colorPrimaryVariant">@color/brown_700</item>
        <item name="colorOnPrimary">@color/white</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/teal_200</item>
        <item name="colorSecondaryVariant">@color/teal_700</item>
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
        <!-- Customize your theme here. -->
        <item name="android:windowContentOverlay">@null</item>
        <item name="actionBarStyle">@style/Theme.Miwok.ActionBar</item>
    </style>
</resources>

6) 完成所有的代码后,应该运行下应用, 确保外观和运行效果跟预期的一样。

在这里插入图片描述

总结

现在已经正式完成 Miwok 应用了!

接下来,无论是继续完善 Miwok 应用,针对其他语言调整该应用,还是展开一段完全不一样的冒险之旅。

接下来准备 学习 Android 开发的网络部分。

参考

What does FragmentManager and FragmentTransaction exactly do?

Remove shadow below actionbar

编译报错:

Execution failed for task ‘:app:mergeExtDexDebug’. While implementing Firebase messaging

Execution failed for task ‘:app:mergeExtDexDebug’. · …

Task :app:mergeDexDebug FAILED - Fantas…hit

Android studio 编译报错 - 简书

猜你喜欢

转载自blog.csdn.net/weixin_45075891/article/details/112797713