动态更改Android应用程序的外观

时间:2015-11-29 17:53:43

标签: android user-interface themes

我为我的大学做了一个Android项目,我们希望它的外观会根据用户的位置而改变。

例如,如果用户位于海域附近,则以海盗为主题,有木制按钮,海洋背景等。 如果用户在山区附近,那可能是以维京为主题。你明白了。

现在我的问题是:实现这样的事情的最佳方式是什么?

提前感谢您的答案:)

1 个答案:

答案 0 :(得分:0)

最好的方法是使用自定义活动主题。使用样式和(自定义)主题属性,您可以将所有可自定义样式放置在主题中。虽然它有点麻烦,但您可以在运行时更改主题。创建视图时应用主题,因此您必须确保重新创建活动。如果您手动处理该更改,则可以提供更好的体验。

您的布局通常如下所示:

典型布局示例

<LinearLayout
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="@drawable/pirate_background">

    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/pirate_greeting"
        />

    <TextView
        android:id="@+id/greeting"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:paddingStart="@dimen/activity_horizontal_margin"
        android:paddingEnd="@dimen/activity_horizontal_margin"
        android:text="@string/pirate_greeting"
        />

</LinearLayout>

现在,如果名为pirate_ *的所有属性都应该是可更改的,那么您希望将它们放在主题中,以便更改主题会自动更改这些元素。最基本的方法是为要自定义的每个属性定义主题属性。这看起来像这样:

使用自定义主题属性的布局

<LinearLayout
    android:id="@+id/layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="?attr/my_layout_background">

    <ImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="?attr/my_image_src"
        />

    <TextView
        android:id="@+id/greeting"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:paddingStart="@dimen/activity_horizontal_margin"
        android:paddingEnd="@dimen/activity_horizontal_margin"
        android:text="?attr/my_greeting_text"
        />

</LinearLayout>
那个问号?表示您指的是主题属性。您可以在资源文件中定义这些属性,通常命名为&#39; attrs.xml&#39;。

<强>值/ attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="my_layout_background" format="reference" />
    <attr name="my_image_src" format="reference" />
    <attr name="my_greeting_text" format="string" />
</resources>

这些属性仅在您的主题为其定义了值时才有效。所以无论你有什么主题,请确保它包含上述属性的定义。

<强>值/的themes.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="MyAppTheme" parent="[your base theme]">
        <item name="my_layout_background">@drawable/default_greeting</item>
        <item name="my_image_src">@drawable/default_greeting</item>
        <item name="my_greeting_text">@string/default_greeting</item>
    </style>

    <style name="MyAppTheme.Pirates">
        <item name="my_layout_background">@drawable/pirate_background</item>
        <item name="my_image_src">@drawable/pirate_greeting</item>
        <item name="my_greeting_text">@string/pirate_greeting</item>
    </style>

    <style name="MyAppTheme.China">
        <item name="my_layout_background">@drawable/china_background</item>
        <item name="my_image_src">@drawable/nihao</item>
        <item name="my_greeting_text">@string/nihao</item>
    </style>

</resources>

为简洁起见,我将假设您知道如何设置主题(如果您不了解,请了解here)。提示:在Android Studio的布局编辑器中,您可以指定一个主题,允许您以不同的主题预览布局。稍微玩一下吧。

到目前为止,您已经完成了简单的部分。我们现在要在运行时更改配置,而这部分并不那么容易。但如果按照我的指示,它就不会太难。对于典型活动,在setContentView(...)方法中创建(或夸大)视图。无论当时的主题是什么,都将决定观点的样子。因此,如果您想以编程方式更改主题,则应确保在调用setContentView(...)之前执行此操作。

设置自定义主题的活动

@Override 
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setTheme(R.style.MyAppTheme_Pirates);
    setContentView(R.layout.activity_main);
    // ...
} 

大多数情况下,您可能希望在创建活动时更改主题。您可以使用recreate()方法,这将破坏当前活动并立即创建一个新活动。当然,如果您销毁活动,您将丢失它所拥有的任何数据(例如您要更改的主题)。有两种方法可以解决这个问题:

  1. 当您确定要应用的主题时,请不要将此数据存储在活动中,将其存储在活动被销毁时不会消失的位置。
  2. 在活动中使用onSaveInstanceState(),并将主题存储在那里。如果您这样做,则可以在创建新活动时恢复此信息。
  3. 您选择哪种方式取决于您。哪个更合适取决于您选择如何构建代码。无论你选择什么,最后你的活动都会有这样的代码:

    重新创建自己以应用新主题的活动

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setTheme(ThemeStorage.getTheme());
        setContentView(R.layout.activity_main);
    }
    
    public void applyNewTheme(@StyleRes int themeResId) {
        ThemeStorage.setTheme(themeResId);
        recreate();
    }
    

    现在您需要做的就是拥有一些代码来确定要应用的主题。这可以是侦听位置更新的后台服务。如果确定应用程序应更改为新主题,则只需在部分或全部正在运行的活动上调用applyNewTheme即可。您可以使用ActivityLifecycleCallbacks获取所有正在运行的活动的列表。您在Application类中注册这些回调。这里提供了一些简单的代码。

    public class MyApplication extends Application {
    
        private ActivityCollector mActivityCollector = new ActivityCollector();
    
        @Override
        public void onCreate() {
           super.onCreate();
           registerActivityLifecycleCallbacks(mActivityCollector.getCallbacks());
        }
    
        public ActivityCollector getActivityCollector() {
            return mActivityCollector;
        }
    }
    
    /**
     * Uses {@link android.app.Application.ActivityLifecycleCallbacks} to 
     * maintain a list of created activities.
     */
    public class ActivityCollector {
    
        private List<Activity> mCreatedActivities = new ArrayList<>();
        private ActivityLifecycleCallbacks mLifecycleCallbacks = new ActivityLifecycleCallbacks() {
    
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                mCreatedActivities.add(activity);
            }
    
            @Override
            public void onActivityDestroyed(Activity activity) {
                mCreatedActivities.remove(activity);
            }
    
            @Override
            public void onActivityStarted(Activity activity) {
            }
    
            @Override
            public void onActivityResumed(Activity activity) {
            }
    
            @Override
            public void onActivityPaused(Activity activity) {
            }
    
            @Override
            public void onActivityStopped(Activity activity) {
            }
    
            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
            }
        };
    
        public List<Activity> getCreatedActivities() {
            return mCreatedActivities;
        }
    
        public ActivityLifecycleCallbacks getCallbacks() {
            return mLifecycleCallbacks;
        }
    }
    

    因此,将ActivityCollector传递给需要将主题应用于您的活动的代码,并且您已完成所有设置。