Extending RadioButton with own style in constructor - android

I'd like to know why my code doesn't work the way I think it should work. I extended the original RadioButton widget with one little change. Here is the code:
<!-- language: java -->
public class RadioButton extends android.widget.RadioButton {
public RadioButton(Context context) {
super(context);
}
public RadioButton(Context context, AttributeSet attrs) {
super(context, attrs, R.style.MyRadioButton);
}
public RadioButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
}
This change is R.style.MyRadioButton in constructor. Next I entered following line to my styles.xml:
<!-- language: xml -->
<style name="MyRadioButton" parent="#android:style/Widget.CompoundButton.RadioButton" />
Widget.CompoundButton.RadioButton is style which defines drawable for RadioButton.
To use my own RadioButton I made a 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" >
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Default radio button" />
<com.example.test.RadioButton
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="My radio button" />
</RadioGroup>
</LinearLayout>
What I think I did is I inherited style from default RadioButton and use that style in constructor (the same way it is done in original RadioButton source code). Problem is that my RadioButton doesn't show any drawable, just text and I was wondering why?
It also does not react on click because tapping it does not uncheck "Default radio button" despite the fact both are in RadioGroup.

Ok, I finally found a problem in my code.
Android documentation says:
defStyleAttr An attribute in the current theme that contains a reference to a style resource to apply to this view. If 0, no default style will be applied.
What I did wrong was passing style instead of attr (reference to style in current theme).
So here is the solution:
public RadioButton(Context context, AttributeSet attrs) {
super(context, attrs, R.attr.myRadioButtonStyle);
}
I left code in styles.xml untouched:
<style name="MyRadioButtonStyle" parent="#android:style/Widget.CompoundButton.RadioButton" />
But i had to create my custom theme in themes.xml and apply it to current Activity:
<style name="MyTheme">
<item name="myRadioButtonStyle">#style/MyRadioButtonStyle</item>
</style>
And in the attrs.xml:
<declare-styleable name="MyTheme">
<attr name="myRadioButtonStyle" format="reference" />
</declare-styleable>
My previous code which did not work was based on someone else's old code where View constructor attribute was named defStyle what led me to mistake. Android developers fixed documentation by renaming attribute to defStyleAttr. Here is an issue which documents it: https://code.google.com/p/android/issues/detail?id=12683
Last post clearly says:
The documentation is incorrect. The third constructor must be an attribute, e.g. R.attr.*

Related

Button not showing Material Design

I've found out that the reason is that I'm using the Android-Iconics library - I removed the context injection and everything is fine now.
I'm using a combination of XML Layouts and Anko DSL to build my app and I've noticed that the button design is different depending on how it's generated.
In case it's an Anko-generated button, the text is in caps (what I think it should be in Material) and has a ripple effect. If the button is created by XML the text is lowercase and without the effect.
The upper button is the XML one, so here you can see the difference.
I've tried setting a custom style to the button but it doesn't seem to work - I can't even make textAllCaps=true work.
Currently I'm using androidx and extending AppCompat & Theme.AppCompat.Light.NoActionBar and I've tried extending Widget.AppCompat.Button to set a custom style to the view without luck.
This is happening in all API levels (24, 26 and 28). In the XML preview it does show fine.
The current XML is
<Button
android:text="#string/compartir"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/debunkShareButton"
android:textAllCaps="true"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintTop_toBottomOf="#+id/debunkTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="#style/Full_Yellow_Button"/>
And the Full_Yellow_Button is
<style name="Full_Yellow_Button" parent="Widget.AppCompat.Button">
<item name="android:background">#drawable/yellow_gradient</item>
</style>
Any ideas? Thanks!
If you are using new material design components make sure your application theme extends from main theme Theme.MaterialComponents or other relavant theme.
<style name="Theme.MyApp" parent="Theme.MaterialComponents.Light">
<!-- ... -->
</style>
Also, instead of using generic Button class to define button, You need to use com.google.android.material.button.MaterialButton in your xml and java both.
<com.google.android.material.button.MaterialButton
android:id="#+id/material_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/button_label_enabled"/>
Your theme should be extended from Theme.MaterialComponents.xxxxx
like this XML block
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
You can create your TextView class for set to uppercase
class UppercaseTextView : TextView, ViewTreeObserver.OnPreDrawListener {
constructor(context: Context) : super(context) {}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {}
override fun setText(text: CharSequence, type: BufferType) {
super.setText(text.toString().toUpperCase(), type)
}
}

Compose a button to use in various parts of my project

I've a button and i need to use this same button with same proprieties in all my projects and i want to create a component to just call this component and if i need to change my button, change in just one class do change all the uses.
I have this button:
<br.com.simplepass.loading_button_lib.customViews.CircularProgressButton
android:id="#+id/createMatch"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:background="#color/buttonColor"
android:layout_marginTop="16dp"
app:spinning_bar_width="4dp"
app:spinning_bar_color="#FFF"
android:alpha="0.5"
android:textColor="#color/colorAccent"
android:elevation="4dp"
android:enabled="false"
android:layout_marginLeft="16dp"
android:textStyle="bold"
android:text="#string/createMatch"
android:textSize="14sp"
app:spinning_bar_padding="6dp"/>
I create a class extending CircularProgressButton but some proprieties i don't have ideia how to access in the code. The proprieties with app: for example.
Merging two questions, how i make a component with a layout with various itens?
for example:
<LinearLayout>
<Toolbar/>
<LinearLayout/>
</LinearLayout>
I want to compose this xml in a class to call and get all this itens in all the layouts using my custom class.
<MyLinearLayout>
...
</MyLinearLayout>
some proprieties i don't have ideia how to access in the code. The proprieties with app: for example.
Have a look here.
You will have to subclass a View and provide a constructor that takes a Context and AttributeSet.
class CircleView extends View {
private String mTitleText = "Your default title";
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
// You can and should also pass a style as third argument
final TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CircleView, 0, 0);
if (a.hasValue(R.styleable.CircleView_titleText)) {
mTitleText = a.getString(R.styleable.CircleView_titleText);
}
a.recycle();
}
}
You might have noticed the use of R.styleable.CircleView_titleText in my example. Before using custom attributes, you have to tell the compiler about them. This is achieved by adding a .xml defining your attributes and their expected format.
<resources>
<declare-styleable name="CircleView">
<attr name="titleText" format="string" />
</declare-styleable>
</resources>
how i make a component with a layout with various itens?
Reusable Layouts is probably what you are looking for.
An example of this would be to define a custom toolbar as toolbar.xml.
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#e40056"
android:elevation="4dp"
android:minHeight="?attr/actionBarSize"
app:titleTextColor="#ffffff"
/>
and then include it in other views:
<include
android:id="#+id/myToolbarId"
layout="#layout/toolbar"
/>
If you take a look at the source code for ProgressBar, you'll see that you can use a TypedArray to retrieve styled attributes from the XML AttributeSet.
public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
...
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes);
...
// this is where we get the drawable that is specified in the XML
final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
...
}
You can do the same to get the app attributes you're looking for, but some of the attributes are marked as internal. In those cases I would consider overriding them in res/values/attrs.xml because they may change.
Note: progressDrawable is not necessarily internal, I just used it as an example
<resources>
<declare-styleable name="ProgressBar">
<attr name="progressDrawable"/>
</declare-styleable>
</resources>
As for your second question, I'm not sure if you want to extend LinearLayout (which I would advise against due to the fact that you would have to write code to create views) or simply reuse the LinearLayout with the views inside it.
If it's the latter, then you can just make an XML file with just your LinearLayout inside of it with the appropriate children, let's call it myLayout.xml. Then, in your other XML files you can just place it in like so:
myLayout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout>
<Toolbar/>
<LinearLayout/>
</LinearLayout>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="#string/lorem_ipsum"/>
<include layout="#layout/myLayout"/>
</LinearLayout>

Override certain attributes of a custom control

Let's say there's a custom control and related style in an Android library project. The application that uses this library wants to override certain attributes of that control, while inheriting the others. In my current approach, I have the following code:
In library/styles.xml:
<style name="CreditCardInputField">
<item name="android:layout_margin">10dp</item>
<item name="android:background">#drawable/border</item>
<item name="android:textStyle">bold|italic</item>
</style>
In app/styles.xml:
<style name="CreditCardInputField">
<item name="android:layout_margin">50dp</item>
</style>
The result I have is that the style from the app completely overrides the style from the library. I.e. I lose the background and textStyle properties, while correctly overriding the layout_margin. This is not what I want, I want to keep the background and textStyle as they're defined in library. Is it possible, and, if yes, how?
EDIT: To clarify, I don't want to use the style directly in the app, only the custom control from the library that uses the style. Therefore creating a new style in the app (with a parent from the library) does effectively nothing.
Use a different name to your style in app/styles.xml and make the other style as it's parent.
<style name="newCreditCardInputField" parent="CreditCardInputField">
<item name="android:layout_margin">50dp</item>
</style>
This will override your layout_margin while restoring background and textStyle.
I've found a way to achieve what I want through the custom attributes. Not as convenient as with a style, but more flexible. In short, declare custom attributes in the library, read them in the control's code, provide in the app. Here's the almost complete code, maybe this will help someone:
In lib/values/attrs.xml (custom attributes are declared here):
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="test_view">
<attr name="field_margins" format="dimension">50dp</attr>
<attr name="field_background" format="reference">#drawable/border</attr>
<attr name="name_field_hint" format="reference"/>
<attr name="number_field_hint" format="reference"/>
</declare-styleable>
</resources>
In lib/layout/credit_card_view.xml (this is the custom control's layout):
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<EditText
style="#style/CreditCardInputField"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<EditText
style="#style/CreditCardInputField"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</merge>
In lib/java/TestView.java (the custom control itself):
public class TestView extends LinearLayout {
public TestView(Context context) {
super(context);
}
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.test_view, 0, 0);
int margins = (int)a.getDimension(R.styleable.test_view_field_margins, 0f);
int background = a.getResourceId(R.styleable.test_view_field_background, R.drawable.border);
int nameFieldHint = a.getResourceId(R.styleable.test_view_name_field_hint, R.string.name_field_hint_lib);
int numberFieldHint = a.getResourceId(R.styleable.test_view_number_field_hint, R.string.number_field_hint_lib);
a.recycle();
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.credit_card_view, this, true);
setOrientation(LinearLayout.VERTICAL);
setGravity(Gravity.CENTER_VERTICAL);
TextView title = (TextView) getChildAt(0);
title.setHint(nameFieldHint);
title.setBackgroundResource(background);
LinearLayout.LayoutParams p = new LinearLayout.LayoutParams(context, attrs);
p.setMargins(margins, margins, margins, margins);
title.setLayoutParams(p);
TextView number = (TextView) getChildAt(1);
number.setHint(numberFieldHint);
number.setBackgroundResource(background);
number.setLayoutParams(p);
}
}
And finally in app/layout/main_activity.xml, custom control's usage and configuration:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res-auto"
...>
<com.example.testlibrary.TestView
custom:field_margins="20dp"
custom:field_background="#drawable/field_background"
custom:name_field_hint="#string/name_field_hint"
custom:number_field_hint="#string/number_field_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
You should use parent attribute e.g.:
<style name="CreditCardInputField" parent="parentStyle">
<item name="android:layout_margin">10dp</item>
<item name="android:background">#drawable/border</item>
<item name="android:textStyle">bold|italic</item>
</style>

How to maintain layout attributes on merged custom android view?

I'm trying to replace a set of views with a custom composite view that is supposed to do exactly the same. Specifically I frequently repeat the following layout:
<LinearLayout style="#style/customLayoutStyle">
<Button style="#style/customButtonStyle" />
<TextView style="#style/customTextViewStyle" />
</LinearLayout>
My goal is to replace this block by a single <Highlighter />.
To this end I define in res/layout/highlighter.xml something like
<merge xmlns:android="http://schemas.android.com/apk/res/android"
style="#style/customLayoutStyle">
<Button android:id="#+id/btnHighlighter" style="#style/customButtonStyle" />
<TextView android:id="#+id/lblHighlighter" style="#style/customTextViewStyle" />
</merge>
And in my custom view I have something like
public class Highlighter extends LinearLayout {
public Highlighter(Context context, AttributeSet attrs) {
super(context, attrs);
inflate(context, R.layout.highlighter, this);
}
}
This mostly works, but it seems some of the layout parameters of the <merge> tag are ignored. This screenshot illustrates what seems to be wrong. The 3 images on the bottom row are aligned correctly, using 3x the LinearLayout block I'm trying to replace. Only the top-left image uses the custom view. My guess is that the layout parameters for padding and layout_weight are lost. Am I doing something wrong, or do I need a workaround?
You're right about the parameters being lost. To workaround this you can put the style definition for Highlighter in the layout where you define the Highlighter.
E.g.
<yournamespace.Highlighter
style="#style/customLayoutStyle"
/>
Years later this is still something that's complicated on Android, but it can be done.
You can do it by using style attribute in your custom view, then setting the style in you theme.
Don't set the style in res/layout/highlighter.xml:
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<Button android:id="#+id/btnHighlighter" style="#style/customButtonStyle" />
<TextView android:id="#+id/lblHighlighter" style="#style/customTextViewStyle" />
</merge>
Then in your custom view:
public class Highlighter extends LinearLayout {
public Highlighter(Context context, AttributeSet attrs) {
// The third constructor parameter is you friend
super(context, attrs, R.attr.highlighterStyle);
inflate(context, R.layout.highlighter, this);
}
}
Define the attribute in values/attrs_highlighter_view.xml:
<resources>
<attr name="highlighterStyle" format="reference" />
</resources>
Now you can set the style in your theme
<style name="YourAppTheme" parent="Theme.AppCompat">
<item name="preferenceViewStyle">#style/customLayoutStyle</item>
</style>
This way this style will be used by default whenever you use Highlighter.
Usage of your custom view is then like you want:
<com.your.app.Highlighter
android:id="#+id/highlighter"
... />
I have not verified if this works with layout_weight, but it does work with padding, background etc.
Elaborating on muscardinus' answer, starting from API 21 (Lollipop), Views accept a defStyleRes as fourth constructor argument, so you can skip the attr part and just do:
In styles.xml
<style name="CustomView">
// Define your style here
</style>
CustomView.kt
class CustomView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : LinearLayout(context, attrs, defStyleAttr, R.style.CustomView) {
...
}
Note that you need at least minSdkVersion 21 for this.

Creating default values for custom attributes using styles and themes

I have several custom Views in which I have created custom styleable attributes that are declared in xml layout and read in during the view's constructor. My question is, if I do not give explicit values to all of the custom attributes when defining my layout in xml, how can I use styles and themes to have a default value that will be passed to my View's constructor?
For example:
attrs.xml:
<declare-styleable name="MyCustomView">
<attr name="customAttribute" format="float" />
</declare-styleable>
layout.xml (android: tags eliminated for simplicity):
<?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/com.mypackage" >
<-- Custom attribute defined, get 0.2 passed to constructor -->
<com.mypackage.MyCustomView
app:customAttribute="0.2" />
<-- Custom attribute not defined, get a default (say 0.4) passed to constructor -->
<com.mypackage.MyCustomView />
</LinearLayout>
After doing more research, I realized that default values can be set in the constructor for the View itself.
public class MyCustomView extends View {
private float mCustomAttribute;
public MyCustomView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.obtainStyledAttributes(attrs,
R.styleable.MyCustomView);
mCustomAttribute = array.getFloat(R.styleable.MyCustomView_customAttribute,
0.4f);
array.recycle();
}
}
The default value could also be loaded from an xml resource file, which could be varied based on screen size, screen orientation, SDK version, etc.

Categories

Resources