What I have:
I have a custom class inheriting AppCompatTextView.
I have defined a custom attribute textformat in attires.xml and
i am passing what font I need to set from the xml
Stylefile
<style name="HeaderFilterName">
<item name="android:src">#drawable/back_button</item>
<item name="android:text">#string/str_filter_edit</item>
<item name="android:gravity">center</item>
<item name="android:textSize">#dimen/Header_Filter_Name_Text_size</item>
<item name="android:layout_weight">1</item>
</style>
XML
<customViews.CustomTftTextView
android:id="#+id/txtScreenNameId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:textformat="fonts/sf_san_fransisco.ttf"
style="#style/HeaderFilterName"/>
attr.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="customfont">
<attr name="textformat" format="string"/>
</declare-styleable>
</resources>
CustomTftTextView.java
public class CustomTftTextView extends AppCompatTextView {
private String text;
public CustomTftTextView(final Context context) {
this(context, null);
Initialize(text,context);
}
public CustomTftTextView(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
text = context.getResources().obtainAttributes(attrs, R.styleable.customfont).getString(R.styleable.customfont_textformat);
Initialize(text,context);
}
public CustomTftTextView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
text = context.getResources().obtainAttributes(attrs, R.styleable.customfont).getString(R.styleable.customfont_textformat);
Initialize(text,context);
}
private void Initialize(String format, Context context) {
Typeface mTypeface;
if (format != null)
{
mTypeface = Typeface.createFromAsset(context.getAssets(), format);
}
else
{
mTypeface = Typeface.createFromAsset(context.getAssets(), "fonts/sf_san_fransisco.ttf");
}
setTypeface(mTypeface, Typeface.NORMAL);
setLineSpacing(0.0f, 1.4f);
}
}
While above code works perfect, If I move app:textformat inside the style file, The font is not setting.
<style name="HeaderFilterName">
<item name="android:src">#drawable/back_button</item>
<item name="android:text">#string/str_filter_edit</item>
<item name="textformat">#string/custom_font_medium </item>
<item name="android:gravity">center</item>
<item name="android:textSize">#dimen/Header_Filter_Name_Text_size</item>
<item name="android:layout_weight">1</item>
</style>
strings.xml
<string name="custom_font_medium">fonts/sf_san_fransisco.ttf</string>
How properly achieve this
If you check the docs for the Resources#obtainAttributes() method, it says:
Retrieve a set of basic attribute values from an AttributeSet, not performing styling of them using a theme and/or style resources.
To get the attributes with your style applied, use a Context#obtainStyledAttributes() method instead. It would also be advisable to keep a reference to that return, so you can recycle() it when done. For example:
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.customfont);
text = a.getString(R.styleable.customfont_textformat);
a.recycle();
Make sure in styles.xml you used namespace like below
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app='http://schemas.android.com/apk/res-auto'>
Related
I'm trying to apply TextInputLayout theme programmatically to create a custom edit text once and use it anywhere.
This is my Custom edit text class:
package com.enjoyapps.weddingapp.view;
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import androidx.appcompat.view.ContextThemeWrapper;
import com.enjoyapps.weddingapp.R;
import com.google.android.material.textfield.TextInputLayout;
public class CustomEditText extends TextInputLayout {
public CustomEditText(Context context) {
super(new ContextThemeWrapper(context, R.style.Widget_MaterialComponents_TextInputLayout_OutlinedBox));
init();
}
public CustomEditText(Context context, AttributeSet attrs) {
// super(context, attrs);
super(new ContextThemeWrapper(context, R.style.Widget_MaterialComponents_TextInputLayout_OutlinedBox), attrs);
init();
}
public CustomEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(new ContextThemeWrapper(context, R.style.Widget_MaterialComponents_TextInputLayout_OutlinedBox), attrs, defStyleAttr);
init();
}
private void init() {
setBoxStrokeColor(Color.BLUE);
setBoxCornerRadii(50,50,50,50);
setBoxBackgroundColor(Color.BLUE);
}
}
As you can see, in Constructor I'm setting the style with :
new ContextThemeWrapper(context, R.style.Widget_MaterialComponents_TextInputLayout_OutlinedBox)
And when i add the custom edit text to xml , it's not getting the attribute that I set in init method.
But when I apply the same theme in xml, it's working and getting the all attribute from init method.
<com.enjoyapps.weddingapp.view.CustomEditText
android:layout_width="300dp"
android:layout_centerInParent="true"
android:layout_height="50dp">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.enjoyapps.weddingapp.view.CustomEditText>
Which style using your app?
Just add to your main style:
<item name="editTextStyle">#style/YourStyle</item>
Example:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">#color/colorPrimary</item>
<item name="colorPrimaryDark">#color/colorPrimaryDark</item>
<item name="colorAccent">#color/colorAccent</item>
<item name="editTextStyle">#style/YourStyle</item>
</style>
It took a bit of digging to find out the problem. Apparently, TextInputLayout uses a boxBackgroundMode in order to use certain attributes from different styles.
In addition to ContextThemeWrapper, you have to set the boxBackgroundMode for your TextInputLayout.
Therefore, if you are using :
FilledBox : you need to use BOX_BACKGROUND_FILLED
OutlinedBox : you need to use BOX_BACKGROUND_OUTLINE
In your case, just add setBoxBackgroundMode(BOX_BACKGROUND_OUTLINE) into your init():
private void init() {
// ... some styling here
setBoxBackgroundMode(BOX_BACKGROUND_OUTLINE);
}
I am having a problem with MaterialCalendarView library.
Everything is fine until I select a day with a decorator because the decorator has the same color of the selection color.
Here is the normal click:
and the issue:
the decorator code:
public class EventDecoratorMonth implements DayViewDecorator {
private CalendarDay date;
private Context context;
public EventDecoratorMonth(CalendarDay date, Context context) {
this.date = date;
this.context = context;
}
#Override
public boolean shouldDecorate(CalendarDay day) {
return day.equals(date);
}
#Override
public void decorate(DayViewFacade view) {
if (context != null)
view.addSpan(new ForegroundColorSpan(ContextCompat.getColor(context, R.color.orange)));
}
}
and through xml the selection color: app:mcv_selectionColor="#color/orange"
the selected day text color is white, I used mcv_dateTextAppearance with a selector to change it to white when android:state_checked="true" and grey in every other case.
The problem is when a day with a decorator is selected that the mcv_dateTextAppearance is not applied.
Edit:
my selector
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#color/white" android:state_checked="true" />
<item android:color="#color/white" android:state_pressed="true" />
<item android:color="#color/textGrey" android:state_enabled="false" />
<item android:color="#color/textGrey" android:state_checked="false" />
<item android:color="#color/textGrey" />
</selector>
How to fix this?
EDIT 2:
I fixed using a decorator with ForegroundColorSpan color set to white, and on changeSelected date I remove old one and set new one
Try using this selector for your text color
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#color/white" android:state_checked="true" />
<item android:color="#color/white" android:state_pressed="true" />
<item android:color="#color/grey" android:state_enabled="false" />
<item android:color="#color/grey" />
</selector>
I'm Working on an android app that has a requirement to switch theme based on the themeCode given from server. I'm using sharePref to save the theme code and applying it with setTheme(R.style.themeName);. Its working fine till the basic theme attributes like
colorPrimary
colorPrimaryDark
colorAccent
windowActionBar
windowNoTitle
For this I has created different styles in styles.xml. But I have a limitation that some fields say EditText has variation as EditText
person name
email
phone
password etc.
And similarly TextView has variation as TextView
Heading
Single Line
Mutiline
Link etc.
Before multi-theme requirement I had created separate themes for all as
Apptheme.Edittext.email
Apptheme.Edittext.Password
Apptheme.Edittext.PersonName etc.
And was applying to specific view in xml like
style="#style/AppTheme.EditText.PersonName"
Now I have viewed many tutorials/posts but did not find solution to the variations in attribute. Please help to apply these variation, I'll be thankful for this.
Regards:
Inzimam Tariq
In my opinion changing app theme at runtime, will definitely need to reload activity; this in most cases will create issues at some point (if project is extended to a mid scale, having a user control like toggle or a switch and if user taps switch repeatedly app may easily crash)
I would suggest to use custom control classes (Textviews, Buttons..etc); wherein this properties are differentiated with current theme value from sharedPref.
This approach has a con; it will require to change all views manually of current screen and those in already rendered in memory(if any), rest all it will be much smoother transition in compare to our conventional approach
EDIT: Example for CustomTextView ##
This is an example for customtextview class
public class CustomTextView extends android.support.v7.widget.AppCompatTextView {
private static final String TAG = "TextView";
private Typeface tf = null;
private SharedPreferenceUtils preferenceUtils = SharedPreferenceUtils.getInstance();
/**
* #param context:This is an abstract class whose implementation is provided by Android Operating System.
* #param attrs:A collection of attributes, as found associated with a tag in an XML document.
* #param defStyle:
*/
public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (preferenceUtils.getBooleanValue(PrefsKeyValue.bTheme)) {
this.setTextColor(ResourceUtils.getColor(R.color.lightThemeTextColor));
} else {
this.setTextColor(ResourceUtils.getColor(R.color.colorWhite));
}
try {
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CustomEditText, defStyle, 0);
String str = a.getString(R.styleable.CustomTextView_FontEnum);
int original = a.getInt(R.styleable.CustomEditText_FontEnum, 0);
CustomEnum.CustomFontType customEnumValue = CustomEnum.CustomFontType.fromId(a.getInt(R.styleable.CustomEditText_FontEnum, 0));
a.recycle();
switch (customEnumValue) {
case BOLD:
setTypeface(HelveticaNeueBold.getInstance(context).getTypeFace());
break;
case LIGHT:
setTypeface(HelveticaNeueMedium.getInstance(context).getTypeFace());
break;
case REGULAR:
setTypeface(HelveticaNeue.getInstance(context).getTypeFace());
break;
default:
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
public CustomTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public boolean setCustomFont(Context ctx, String asset) {
try {
tf = Typeface.createFromAsset(ctx.getAssets(), asset);
} catch (Exception e) {
LogUtils.LogE(TAG, "Could not get typeface: " + e.getMessage());
return false;
}
setTypeface(tf);
return true;
}}
Herein I have changed textcolor in accordance to theme value from sharedPref
if (preferenceUtils.getBooleanValue(PrefsKeyValue.bTheme)) {
this.setTextColor(ResourceUtils.getColor(R.color.lightThemeTextColor));
} else {
this.setTextColor(ResourceUtils.getColor(R.color.colorWhite));
}
Then use this class as textview tag in xml file.
<com.mypkg.customview.CustomTextView
style="#style/signup_textViewStyle"
android:text="#string/activity_login_password" />
I believe, you can handle property variation with theme for controls in same manner.
What you can do is create custom attributes for your view types (e.g. TextView.Person, TextView.Date...), in your xml you can reference the attributes and then define the attributes in different themes. For instance, you style.xml could be
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar" >
<item name="TextView.Date">#style/DateTextViewDefault</item>
</style>
<style name="DateTextViewDefault">
<item name="android:textColor">#ff333333</item>
<item name="android:fontFamily">monospace</item>
</style>
<!-- Theme A -->
<style name="AppTheme.A">
<item name="colorPrimary">#3F51B5</item>
<item name="colorPrimaryDark">#303F9F</item>
<item name="colorAccent">#FF4081</item>
<item name="TextView.Person">#style/PersonTextViewA</item>
</style>
<style name="PersonTextViewA">
<item name="android:textSize">16sp</item>
<item name="android:fontFamily">serif</item>
<item name="android:textColor">#ff999999</item>
</style>
<!-- Theme B -->
<style name="AppTheme.B">
<item name="colorPrimary">#888888</item>
<item name="colorPrimaryDark">#555555</item>
<item name="colorAccent">#000000</item>
<item name="TextView.Person">#style/PersonTextViewB</item>
<item name="TextView.Date">#style/DateTextViewB</item>
</style>
<style name="PersonTextViewB">
<item name="android:textSize">20sp</item>
<item name="android:fontFamily">monospace</item>
<item name="android:textColor">#ff55aa</item>
</style>
<style name="DateTextViewB">
<item name="android:textColor">#ff0000BB</item>
<item name="android:fontFamily">sans-serif</item>
</style>
<attr name="TextView.Person" format="reference" />
<attr name="TextView.Date" format="reference" />
</resources>
then your activity xml layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
style="?attr/TextView.Person"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="John Doe" />
<TextView
style="?attr/TextView.Date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="31/12/1999" />
<Button
android:id="#+id/buttonA"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="THEME A" />
<Button
android:id="#+id/buttonB"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="THEME B" />
</LinearLayout>
notice that the styles of the TextView are
style="?attr/TextView.Person"
and
style="?attr/TextView.Date"
AppTheme.A and AppTheme.B have 2 different resolutions for those attributes
In this example the attributes are entire styles for your views but you could easily have one style per view type (TextView.person) and then define generic attributes for single items of that style, e.g.
<attr name="TextView.Person.TextColor" format="color" />
and only change that single attribute in your themes.
Then in your Activity you just need to set the theme in onCreate with setTheme(int), the value could be in this case either R.style.AppTheme_A or R.style.AppTheme_B.
With this method you can add as many styles as you want without touching the layouts. Also, you can always define some default styles in your base theme and then only override that value in some of the custom themes, while others use the default one as for TextView.Date in the sample above.
If you want to give it a quick try, here's the code of the Activity I used to test style.xml and activity_main.xml above
class MainActivity : AppCompatActivity() {
private val prefs by lazy { getSharedPreferences("SharedPrefs", Context.MODE_PRIVATE) }
private var customTheme: Int
get() = prefs.getInt("theme", R.style.AppTheme_A)
set(value) = prefs.edit()
.putInt("theme", value)
.apply()
.also { recreate() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTheme(customTheme)
setContentView(R.layout.activity_main)
buttonA.setOnClickListener { customTheme = R.style.AppTheme_A }
buttonB.setOnClickListener { customTheme = R.style.AppTheme_B }
}
}
I would like to define what color should be used when painting to a canvas depending on a custom state. This is how far I got:
In res/layout/content.xml:
<com.example.package.MyView
app:primary_color="#drawable/my_selector"
/>
primary_color is a custom attribute defined in res/values/attrs.xml:
<resource>
<declare-styleable name="MyView">
<attr name="primary_color" format="reference"/>
</declare-styleable>
</resource>
my_selector is defined in res/drawable/my_selector.xml
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.example.package">
<item
app:state_a="true"
android:drawable="#drawable/red" />
<item
app:state_b="true"
android:drawable="#drawable/orange" />
<item
app:state_c="true"
android:drawable="#drawable/red" />
</selector>
red, orange and red are defined in res/values/colordrawable.xml:
<resources>
<drawable name="red">#f00</drawable>
<drawable name="orange">#fb0</drawable>
<drawable name="green">#0f0</drawable>
</resources>
In MyView I can get this drawable:
StateListDrawable primaryColor;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
try{
primaryColor = (StateListDrawable) a.getDrawable(
R.styleable.MyView_primary_color);
}finally {
a.recycle();
}
}
primaryColor updates correctly with the different states, I can test this by calling:
setBackground(primaryColor);
But I want to use this color with Paint, like this:
paint.setColor(primaryColor);
But this is obviously not allowed. I've tried converting the primaryColor to a ColorDrawable which has the method getColor(), but I can't figure out how to do this, if it is possible.
Any suggestions on how to get the color that can be used in the the view from a selector would be amazing.
I found ColorStateList which turned out to be exactly what I needed. The following is a simplified version of my current implementation, in case someone else gets in the same rut as I did.
In res/color/my_selector.xml
<?xml version="1.0" encoding="utf-8" ?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"">
<item app:state_weak="true" android:color="#F00" />
<item app:state_average="true" android:color="#0F0" />
<item app:state_strong="true" android:color="#00F" />
<item android:color="#FA0" />
</selector>
In res/layout/content.xml (This has another layout wrapped around it, but that is not relevant)
<com.example.package.MyView
android:id="#+id/strMeter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:primary_color="#color/my_selector"
/>
primary_color is defines as a reference in res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyView">
<attr name="primary_color" format="reference"/>
</declare-styleable>
</resources>
I get the reference to the ColorStateList in the constructor of MyView:
ColorStateList primaryColor;
public PasswordStrengthBar(Context context, AttributeSet attrs) {
super(context, attrs);
try{
primaryColor = a.getColorStateList(
R.styleable.MyView_primary_color);
}finally {
a.recycle();
}
}
When I want to get the color for the current state:
int color = secondaryColor.getColorForState(
getDrawableState(), primaryColor.getDefaultColor());
If you implement a custom state, like I did, then you will also have to override onCreateDrawableState for the states to actually update, but there is plenty of documentation/posts that cover that.
In order to change the font which is inside my actionbar, I implemented a custom TypefaceSpan class and used a SpannableString in my onCreate.
This has what I did:
How to Set a Custom Font in the ActionBar Title?
In the comments user, artwork ,said,
In the moment of starting the app, the default title style is visible
and about 1 sec later the custom style appears.
Then the guy who answered the question said
"that's because your activity's theme is being applied to the
DecorView before your code has spun up. You can work around this by
hiding the action bar in your theme, then show it as your activity is
being created."
I am not sure how to implement the following. Nor am I sure that is able to be done in my customized action bar.
And this is how I styled my actionbar:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Customaction" parent="#style/android:Theme.Holo">
<item name="actionBarItemBackground">#drawable/selectable_background_customaction</item>
<item name="popupMenuStyle">#style/PopupMenu.Customaction</item>
<item name="dropDownListViewStyle">#style/DropDownListView.Customaction</item>
<item name="actionBarTabStyle">#style/ActionBarTabStyle.Customaction</item>
<item name="actionDropDownStyle">#style/DropDownNav.Customaction</item>
<item name="actionBarStyle">#style/ActionBar.Transparent.Customaction</item>
<item name="actionModeBackground">#drawable/cab_background_top_customaction</item>
<item name="actionModeSplitBackground">#drawable/cab_background_bottom_customaction</item>
<item name="actionModeCloseButtonStyle">#style/ActionButton.CloseMode.Customaction</item>
</style>
<style name="ActionBar.Solid.Customaction" parent="#style/Widget.AppCompat.ActionBar.Solid">
<item name="background">#drawable/ab_solid_customaction</item>
<item name="backgroundStacked">#drawable/ab_stacked_solid_customaction</item>
<item name="backgroundSplit">#drawable/ab_bottom_solid_customaction</item>
<item name="progressBarStyle">#style/ProgressBar.Customaction</item>
</style>
<style name="ActionBar.Transparent.Customaction" parent="#style/Widget.AppCompat.ActionBar">
<item name="background">#drawable/ab_transparent_customaction</item>
<item name="progressBarStyle">#style/ProgressBar.Customaction</item>
</style>
<style name="PopupMenu.Customaction" parent="#style/Widget.AppCompat.PopupMenu">
<item name="android:popupBackground">#drawable/menu_dropdown_panel_customaction</item>
</style>
<style name="DropDownListView.Customaction" parent="#style/Widget.AppCompat.ListView.DropDown">
<item name="android:listSelector">#drawable/selectable_background_customaction</item>
</style>
<style name="ActionBarTabStyle.Customaction" parent="#style/Widget.AppCompat.ActionBar.TabView">
<item name="android:background">#drawable/tab_indicator_ab_customaction</item>
</style>
<style name="DropDownNav.Customaction" parent="#style/Widget.AppCompat.Spinner.DropDown.ActionBar">
<item name="android:background">#drawable/spinner_background_ab_customaction</item>
<item name="android:popupBackground">#drawable/menu_dropdown_panel_customaction</item>
<item name="android:dropDownSelector">#drawable/selectable_background_customaction</item>
</style>
<style name="ProgressBar.Customaction" parent="#style/Widget.AppCompat.ProgressBar.Horizontal">
<item name="android:progressDrawable">#drawable/progress_horizontal_customaction</item>
</style>
<style name="ActionButton.CloseMode.Customaction" parent="#style/Widget.AppCompat.ActionButton.CloseMode">
<item name="android:background">#drawable/btn_cab_done_customaction</item>
</style>
<!-- this style is only referenced in a Light.DarkActionBar based theme -->
<style name="Theme.Customaction.Widget" parent="#style/Theme.AppCompat">
<item name="popupMenuStyle">#style/PopupMenu.Customaction</item>
<item name="dropDownListViewStyle">#style/DropDownListView.Customaction</item>
</style>
</resources>
This is part of what I have in my MainActivity:
#Override
protected void onCreate(Bundle savedInstanceState) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
super.onCreate(savedInstanceState);
//Make sure you find out why it appears after a whole 1 second after the app appears
SpannableString s = new SpannableString("GetDisciplined");
s.setSpan(new TypefaceSpan(this, "roboto-lightitalic.ttf"), 0, s.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// Update the action bar title with the TypefaceSpan instance
ActionBar actionBar = getActionBar();
actionBar.setTitle(s);
setContentView(R.layout.merge);
I am just trying to solve the problem of the Custom ActionBar Font showing up after one second by making it show up immediately, but I am unsure to achieve this.
One way you could do it (not a very nice way) is to use a custom layout for your titlebar where you use a custom textview that displays the title.
public class CustomTextView extends TextView {
private static Typeface customFont = null;
public CustomTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (isInEditMode()) {
return;
}
if (customFont == null) {
customFont = Typeface.createFromAsset(context.getApplicationContext().getAssets(),
"fonts/custom_font.ttf");
}
setTypeface(customFont);
}
}
In your activity:
requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
setContentView(R.layout.activities);
getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_title);
And the title in the layout uses that CustomTextView to hold the title.
Once again, pretty messy solution, but will do what you want it to.