Reputation: 1220
In my app, I have two themes (light and dark), and I want all of my CardView
s to change their background color depending on which theme is selected.
What I don't want is:
<android.support.v7.widget.CardView
style="@style/CardView.MyBlue"
android:layout_width="200dp"
android:layout_height="100dp"
android:layout_gravity="center_horizontal">
The above code is not dynamic. I need my two styles to automatically be applied depending on if a light or a dark theme is selected.
What I have right now doesn't work:
<style name="AppTheme.Light" parent="Theme.AppCompat.Light">
...
<item name="cardViewStyle">@style/CardViewStyle.Light</item>
</style>
<style name="AppTheme.Dark" parent="Theme.AppCompat">
...
<item name="cardViewStyle">@style/CardViewStyle.Dark</item>
</style>
<style name="CardViewStyle.Light" parent="CardView">
<item name="cardBackgroundColor">@color/cardview_dark_background</item>
</style>
<style name="CardViewStyle.Dark" parent="CardView">
<item name="cardBackgroundColor">@color/cardview_light_background</item>
</style>
I read somewhere that you can define a styleable.xml
file in /res/values/
to key the word cardViewStyle
, so I did that:
styleable.xml:
<resources>
<declare-styleable name="AppTheme">
<attr name="cardViewStyle" format="reference" />
</declare-styleable>
</resources>
Similar question here with no answer either.
Upvotes: 24
Views: 16913
Reputation: 9621
If you don't want to add style="?cardViewStyle"
to every CardView
in your layouts, you can subclass CardView
and set the style attribute in the constructor (too bad they didn't do that in the support library). Then use that subclass in the XML, instead of CardView
.
package com.example.widget;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.CardView;
import android.util.AttributeSet;
import com.example.R;
/**
* {@link CardView} subclass that allows theme'ing through
* the {@link R.attr#cardViewStyle} attribute.
* <p>
* The attribute needs to be defined, for example in <code>attrs.xml</code> as:
* <pre>
* <attr format="reference" name="cardViewStyle"/>
* </pre>
* <p>
* You'll need to set that attribute in your theme, as usual:
* <pre>
* <item name="cardViewStyle">@style/CardView.MyStyle</item>
* </pre>
* And define the style itself, for example:
* <pre>
* <style name="CardView.MyStyle" parent="CardView.Light">
* <item name="cardCornerRadius">0dp</item>
* </style>
* </pre>
*/
public class StylishCardView extends CardView {
public StylishCardView(@NonNull Context context) {
this(context, null);
}
public StylishCardView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.cardViewStyle);
}
public StylishCardView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
The attribute cardViewStyle
needs to be defined, for example in attrs.xml
as:
<attr format="reference" name="cardViewStyle"/>
You'll need to set that attribute in your theme, as usual:
<item name="cardViewStyle">@style/CardView.MyStyle</item>
And define the style itself, for example:
<style name="CardView.MyStyle" parent="CardView.Light">
<item name="cardCornerRadius">0dp</item>
</style>
Upvotes: 2
Reputation: 101
To show more detail about how to implement David Park's solution...
Put this in attrs.xml:
<declare-styleable name = "cardStyle">
<attr name="cardStyle" format="reference" />
</declare-styleable>
<style name="Light" parent="Theme.AppCompat.NoActionBar">
<item name="cardStyle">@style/CardView.Light</item>
</style>
<style name="Dark" parent="Theme.AppCompat.NoActionBar">
<item name="cardStyle">@style/CardView.Dark</item>
</style>
Then, add cardStyle to your theme in styles.xml:
<style name="AppThemeLight" parent="AppTheme.Base"/>
<style name="AppTheme.Base" parent="Theme.AppCompat.Light.NoActionBar">
<item name="cardStyle">@style/CardView.Light</item>
</style>
<style name="AppThemeDark" parent="AppTheme.Base.Dark"/>
<style name="AppTheme.Base.Dark" parent="Theme.AppCompat.NoActionBar">
<item name="cardStyle">@style/CardView.Dark</item>
</style>
Then use the attr wherever you have a CardView:
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="@+id/header_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
card_view:cardCornerRadius="2dp"
card_view:cardElevation="2dp"
style="?attr/cardStyle">
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:id="@+id/header_title"
</android.support.v7.widget.CardView>
Upvotes: 10
Reputation: 439
In styles.xml
<resources>
<attr format="reference" name="cardStyle"/>
<style name="Light" parent="Theme.AppCompat.NoActionBar">
<item name="cardStyle">@style/CardView.Light</item>
...
</style>
<style name="Dark" parent="Theme.AppCompat.NoActionBar">
<item name="cardStyle">@style/CardView.Dark</item>
...
</style>
</resources>
Then in your other xml to use the new attribute, you would use it like this
style="?attr/cardStyle"
Upvotes: 21
Reputation: 1220
The only solution I've been able to come up with is to manually hold a constant variable for the card color should be after every theme change. Then, I get every instance of a card that is in my application and set its color to that constant variable.
So, I have a BaseActivity from which all of my activities extend from. In it, I handle the card color change in addition to handling the theme. See below:
BaseActivity.java
:
public class BaseActivity extends ActionBarActivity {
private static final int DEFAULT_THEME_ID = R.style.AppTheme_Dark;
@Override
protected void onCreate(Bundle savedInstanceState) {
int theme_id =
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getInt("THEME_ID", DEFAULT_THEME_ID);
setTheme(theme_id);
int card_color;
if (theme_id == DEFAULT_THEME_ID){
card_color = R.color.cv_dark;
} else{
card_color = R.color.cv_light;
}
Constants.CARD_COLOR = card_color;
super.onCreate(savedInstanceState);
}
}
This is definitely not the best way to do this, but until someone else can find a better way to implement styling, this is all I can think of.
Upvotes: 0