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.
Related
I searched for many answers but didn't find any working solution. I wonder if there is some possibility to set Edittext style programmatically. In my situation, I have one layout which is included and used in two different places and beside the place where it is included need to use a different style.
<style name="TestStyle1" parent="Widget.AppCompat.EditText">
<item name="android:maxLength">1</item>
<item name="android:singleLine">true</item>
</style>
<style name="TestStyle2" parent="CodeTextStyle">
<item name="android:alpha">0.5</item>
</style>
first_activity.xml
<include
layout="#layout/custom_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
second_activity.xml
<include
layout="#layout/custom_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
custom_layout.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CustomEditText
android:id="#+id/et"
app:style_ref="#style/CustomTextStyle"
style="#style/TestStyle1" <-- Here i need to change style beside of usage
android:layout_height="wrap_content" />
</LinearLayout>
CustomEditText.kt
class CustomEditText #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : AppCompatEditText(context, attrs) {
init {
val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.CustomEditText, 0 , 0)
try {
val style2 = typedArray.getResourceId(R.styleable.CustomEditText_style_ref, 0)
setTypeface(Typeface.DEFAULT, style2)
invalidate()
}
}
}
So the simplest way, if your min API level is 23 would be to get a reference of each CustomEditText instance after inflation of the respective activity layout, then just set
myCustomEditText.setTextAppearance(R.style.TestStyle1)
as explained in this answer (which is using the deprecated version of this)
Another option would be to apply the 2 styles to two respective app theme styles, which you can set to each activity in AndroidManifest.
Or you can go the more complicated path that I suggested in my comment:
Create a custom LinearLayout class and pass the style in the constructor:
class CustomLayout(context: Context) : LinearLayout(context, null, R.style.TestStyle1)
You would just need to figure out a way to let the layout know which activity it is created. As far as I know, that style would then get passed on to each child view, your CustomEditText respectively.
Two useful answers.
RelativeLayout layout = (RelativeLayout)getLayoutInflater().inflate(R.layout.template, null);
or
int buttonStyle = R.style.your_button_style;
Button button = new Button(new ContextThemeWrapper(context, buttonStyle), null, buttonStyle).
See https://stackoverflow.com/a/24438579/5093308 and https://stackoverflow.com/a/5488652/5093308
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)
}
}
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>
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.*
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.