android – Attach custom CardView style to theme-ThrowExceptions

Exception or error:

In my app, I have two themes (light and dark), and I want all of my CardViews 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>

Update

Similar question here with no answer either.

How to solve:

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"

###

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>

###

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>
 *   &lt;attr format="reference" name="cardViewStyle"/&gt;
 * </pre>
 * <p>
 * You'll need to set that attribute in your theme, as usual:
 * <pre>
 *   &lt;item name="cardViewStyle"&gt;@style/CardView.MyStyle&lt;/item&gt;
 * </pre>
 * And define the style itself, for example:
 * <pre>
 *   &lt;style name="CardView.MyStyle" parent="CardView.Light"&gt;
 *     &lt;item name="cardCornerRadius"&gt;0dp&lt;/item&gt;
 *   &lt;/style&gt;
 * </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>

###

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.

Leave a Reply

Your email address will not be published. Required fields are marked *