Set an XML file as a custom View? - android

I have a custom Table Row that I am in the process of making. I want to use an XML file to define what a single row looks like. I would like to have a class extend TableRow and define itself to be the file as defined in the XML. The XML file might look like:
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<TextView
android:id="#+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="10dp"
android:text="#string/loading"
android:textAppearance="?android:attr/textAppearanceLarge" />
<TextView
android:id="#+id/data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:text="#string/loading"
android:textAppearance="?android:attr/textAppearanceLarge" />
</TableRow>
And the code might look like:
public class SpecialTableRow extends TableRow {
public SpecialTableRow (Context context) {
}
}
Is there something that I can put into the constructor to have the class assume it is the tableRow in it's entirety? Alternatively, is there another structure which would work better? The best that I've figured out is this:
TableRow tr=(TableRow) LayoutInflater.from(context).inflate(R.layout.text_pair,null);
TextView mFieldName=(TextView) tr.findViewById(R.id.label);
TextView mValue=(TextView) tr.findViewById(R.id.data);
tr.removeAllViewsInLayout();
addView(mFieldName);
addView(mValue);
But this removes the layout parameters from the XML. Anything better out there?

Take a look at the tutorial on creating custom views. You will want to subclass TableRow and add the additional views you want to display. Then, you can use your new view directly in your XML layouts and additionally create any custom attributes you might want. I've included an example which creates a custom TableRow named TextPairRow, inflates a layout with two TextViews to show within the TableRow and adds showLabel and showData custom attributes which show/hide the two TextViews. Finally, I've included how you would use your new view directly in your XML layouts.
class TextPairRow extends TableRow {
private TextView label, data;
public TextPairRow (Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.TextPairRow, 0, 0);
try {
showLabel = a.getBoolean(R.styleable.TextPairRow_showLabel, false);
showData = a.getBoolean(R.styleable.TextPairRow_showData, false);
} finally {
a.recycle();
}
initViews();
}
private void initViews(){
// Here you can inflate whatever you want to be in your
// view or add views programatically.
// In this example, we'll just assume you have a basic XML
// layout which defines a LinearLayout with two TextViews.
LinearLayout mLayout = (LinearLayout)
LayoutInflater.from(getContext()).inflate(R.layout.textview_layout, this);
label = (TextView) mLayout.findViewById(R.id.label);
data = (TextView) mLayout.findViewById(R.id.data);
if(showLabel)
label.setVisibility(View.VISIBLE);
else
label.setVisibility(View.GONE); // can also use View.INVISIBLE
// depending on your needs
if(showData){
data.setVisibility(View.VISIBLE);
else
data.setVisibility(View.GONE); // can also use View.INVISIBLE
// depending on your needs
}
}
This is where you define your custom XML attributes (locate or create this file: res/values/attrs.xml)
<resources>
<declare-styleable name="TextPairRow">
<attr name="showText" format="boolean" />
<attr name="showLabel" format="boolean" />
</declare-styleable>
</resources>
Finally, to use your new view directly in your XML layouts:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<com.thefull.packageforyourview.TextPairRow
android:orientation="horizontal"
custom:showData="true"
custom:showLabel="true" />
</LinearLayout>
Note that you might need to use xmlns:custom="http://schemas.android.com/apk/res/com.thefull.packageforyourview" depending on if your custom view will be in a library project. Regardless, either this or what's in the example will work.

The real trick to doing this is actually quite simple. Use the second parameter of the inflate method. In fact, the best thing to do is this:
LayoutInflater.from(context).inflate(R.layout.text_pair,this);
This will inflate the R.layout.text_pair into this, effectively using the entire row. No need to add the view manually, Android takes care of it for you.

The only thing I can think of is to use a static method instead of constructor. For example:
public static void newInstance (Context context) {
this = context.getLayoutInflater().inflate(R.layout.text_pair, null, null);
}
Then don't use constructor for initializing an object, call this method.

Related

How can I change style of views dynamically? [duplicate]

Here's XML:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
style="#style/LightStyle"
android:layout_width="fill_parent"
android:layout_height="55dip"
android:clickable="true"
android:orientation="horizontal" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" />
</RelativeLayout>
How to set style attribute programmatically?
Technically you can apply styles programmatically, with custom views anyway:
private MyRelativeLayout extends RelativeLayout {
public MyRelativeLayout(Context context) {
super(context, null, R.style.LightStyle);
}
}
The one argument constructor is the one used when you instantiate views programmatically.
So chain this constructor to the super that takes a style parameter.
RelativeLayout someLayout = new MyRelativeLayout(new ContextThemeWrapper(this,R.style.RadioButton));
Or as #Dori pointed out simply:
RelativeLayout someLayout = new RelativeLayout(new ContextThemeWrapper(activity,R.style.LightStyle));
Now in Kotlin:
class MyRelativeLayout #JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = R.style.LightStyle,
) : RelativeLayout(context, attributeSet, defStyleAttr)
or
val rl = RelativeLayout(ContextThemeWrapper(activity, R.style.LightStyle))
What worked for me:
Button b = new Button(new ContextThemeWrapper(this, R.style.ButtonText), null, 0);
Use a ContextThemeWrapper
AND
Use the 3-arguments constructor (won't work without this)
Update: At the time of answering this question (mid 2012, API level 14-15), setting the view programmatically was not an option (even though there were some non-trivial workarounds) whereas this has been made possible after the more recent API releases. See #Blundell's answer for details.
OLD Answer:
You cannot set a view's style programmatically yet, but you may find this thread useful.
For a new Button/TextView:
Button mMyButton = new Button(new ContextThemeWrapper(this, R.style.button_disabled), null, 0);
For an existing instance:
mMyButton.setTextAppearance(this, R.style.button_enabled);
For Image or layouts:
Image mMyImage = new ImageView(new ContextThemeWrapper(context, R.style.article_image), null, 0);
This is quite old question but solution that worked for me now is to use 4th parameter of constructor defStyleRes - if available.. on view... to set style
Following works for my purposes (kotlin):
val textView = TextView(context, null, 0, R.style.Headline1)
If you'd like to continue using XML (which the accepted answer doesn't let you do) and set the style after the view has been created you may be able to use the Paris library which supports a subset of all available attributes.
Since you're inflating your view from XML you'd need to specify an id in the layout:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/my_styleable_relative_layout"
style="#style/LightStyle"
...
Then when you need to change the style programmatically, after the layout has been inflated:
// Any way to get the view instance will do
RelativeLayout myView = findViewById(R.id.my_styleable_relative_layout);
// This will apply all the supported attribute values of the style
Paris.style(myView).apply(R.style.LightStyle);
For more: the list of supported view types and attributes (includes background, padding, margin, etc. and can easily be extended) and installation instructions with additional documentation.
Disclaimer: I'm the original author of said library.
You can apply a style to your activity by doing:
super.setTheme( R.style.MyAppTheme );
or Android default:
super.setTheme( android.R.style.Theme );
in your activity, before setContentView().
Non of the provided answers are correct.
You CAN set style programatically.
Short answer is take a look at http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/content/Context.java#435
Long answer.
Here's my snippet to set custom defined style programatically to your view:
1) Create a style in your styles.xml file
<style name="MyStyle">
<item name="customTextColor">#39445B</item>
<item name="customDividerColor">#8D5AA8</item>
</style>
Do not forget to define your custom attributes in attrs.xml file
My attrsl.xml file:
<declare-styleable name="CustomWidget">
<attr name="customTextColor" format="color" />
<attr name="customDividerColor" format="color" />
</declare-styleable>
Notice you can use any name for your styleable (my CustomWidget)
Now lets set the style to the widget Programatically
Here's My simple widget:
public class StyleableWidget extends LinearLayout {
private final StyleLoader styleLoader = new StyleLoader();
private TextView textView;
private View divider;
public StyleableWidget(Context context) {
super(context);
init();
}
private void init() {
inflate(getContext(), R.layout.widget_styleable, this);
textView = (TextView) findViewById(R.id.text_view);
divider = findViewById(R.id.divider);
setOrientation(VERTICAL);
}
protected void apply(StyleLoader.StyleAttrs styleAttrs) {
textView.setTextColor(styleAttrs.textColor);
divider.setBackgroundColor(styleAttrs.dividerColor);
}
public void setStyle(#StyleRes int style) {
apply(styleLoader.load(getContext(), style));
}
}
layout:
<TextView
android:id="#+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="22sp"
android:layout_gravity="center"
android:text="#string/styleble_title" />
<View
android:id="#+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"/>
</merge>
And finally StyleLoader class implementation
public class StyleLoader {
public StyleLoader() {
}
public static class StyleAttrs {
public int textColor;
public int dividerColor;
}
public StyleAttrs load(Context context, #StyleRes int styleResId) {
final TypedArray styledAttributes = context.obtainStyledAttributes(styleResId, R.styleable.CustomWidget);
return load(styledAttributes);
}
#NonNull
private StyleAttrs load(TypedArray styledAttributes) {
StyleAttrs styleAttrs = new StyleAttrs();
try {
styleAttrs.textColor = styledAttributes.getColor(R.styleable.CustomWidget_customTextColor, 0);
styleAttrs.dividerColor = styledAttributes.getColor(R.styleable.CustomWidget_customDividerColor, 0);
} finally {
styledAttributes.recycle();
}
return styleAttrs;
}
}
You can find fully working example at https://github.com/Defuera/SetStylableProgramatically
This is my simple example, the key is the ContextThemeWrapper wrapper, without it, my style does not work, and using the three parameters constructor of the View.
ContextThemeWrapper themeContext = new ContextThemeWrapper(this, R.style.DefaultLabelStyle);
TextView tv = new TextView(themeContext, null, 0);
tv.setText("blah blah ...");
layout.addView(tv);
the simple way is passing through constructor
RadioButton radioButton = new RadioButton(this,null,R.style.radiobutton_material_quiz);
I don't propose to use ContextThemeWrapper as it do this:
The specified theme will be applied on top of
the base context's theme.
What can make unwanted results in your application. Instead I propose new library "paris" for this from engineers at Airbnb:
https://github.com/airbnb/paris
Define and apply styles to Android views programmatically.
But after some time of using it I found out it's actually quite limited and I stopped using it because it does not support a lot of properties i need out off the box, so one have to check out and decide as always.
int buttonStyle = R.style.your_button_style;
Button button = new Button(new ContextThemeWrapper(context, buttonStyle), null, buttonStyle);
Only this answer works for me. See https://stackoverflow.com/a/24438579/5093308
best simple solution i found, using alertDialog with a custom layout, is :
val mView = LayoutInflater.from(context).inflate(layoutResId, null)
val dialog = AlertDialog.Builder(context, R.style.CustomAlertDialog)
.setView(mView)
.setCancelable(false)
.create()
where style is
<style name="CustomAlertDialog" parent="Theme.AppCompat.Light.Dialog.Alert">
<item name="android:background">#drawable/bg_dialog_white_rounded</item>
</style>
and bg_dialog_white_rounded.xml is
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="16dp" />
<solid android:color="#Color/white" />
</shape>
layoutResId is a resource id of any layout that has to have the theme set to "#style/CustomAlertDialog", for example:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="#dimen/wdd_margin_medium"
android:theme="#style/CustomAlertDialog"
android:layout_marginEnd="#dimen/wdd_margin_medium">
..... etc...
</androidx.constraintlayout.widget.ConstraintLayout>
I used views defined in XML in my composite ViewGroup, inflated them added to Viewgroup. This way I cannot dynamically change style but I can make some style customizations. My composite:
public class CalendarView extends LinearLayout {
private GridView mCalendarGrid;
private LinearLayout mActiveCalendars;
private CalendarAdapter calendarAdapter;
public CalendarView(Context context) {
super(context);
}
public CalendarView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
init();
}
private void init() {
mCalendarGrid = (GridView) findViewById(R.id.calendarContents);
mCalendarGrid.setNumColumns(CalendarAdapter.NUM_COLS);
calendarAdapter = new CalendarAdapter(getContext());
mCalendarGrid.setAdapter(calendarAdapter);
mActiveCalendars = (LinearLayout) findViewById(R.id.calendarFooter);
}
}
and my view in xml where i can assign styles:
<com.mfitbs.android.calendar.CalendarView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="vertical"
>
<GridView
android:id="#+id/calendarContents"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:id="#+id/calendarFooter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
/>
if inside own custom view :
val editText = TextInputEditText(context, attrs, defStyleAttr)
You can create the xml containing the layout with the desired style and then change the background resource of your view, like this.

Alternatives to creating a Compound Control by extending a Layout

I want to create a custom Compound Control in Android that holds some logic. For the purpose of this example, let's say I want it to switch between two views when clicked.
According to the API guide, it looks like the way to do that is to create a new class that extends Layout, and do everything in there.
So I did just that:
I created a XML layout to inflate for my custom component:
.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/view1"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="Hello"/>
<TextView
android:id="#+id/view2"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="World"
android:visibility="gone"/>
</RelativeLayout>
Then I created my custom Layout class, and added the logic in there:
public class MyWidget extends RelativeLayout {
public final View mView1;
public final View mView2;
public MyWidget(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
RelativeLayout view = (RelativeLayout) inflater.inflate(R.layout.my_widget, this, true);
mView1 = view.findViewById(R.id.view1);
mView2 = view.findViewById(R.id.view2);
view.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
switchViews();
}
});
}
public void switchViews() {
if (mView1.getVisibility() == View.VISIBLE) {
mView1.setVisibility(View.GONE);
} else {
mView1.setVisibility(View.VISIBLE);
}
if (mView2.getVisibility() == View.VISIBLE) {
mView2.setVisibility(View.GONE);
} else {
mView2.setVisibility(View.VISIBLE);
}
}
}
And finally, I included my custom view in some layout:
.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.example.MyWidget
android:layout_height="match_parent"
android:layout_width="match_parent"/>
</RelativeLayout
And that works.
I am not completely happy with that solution though, for 2 reasons:
In the constructor of MyWidget, I instantiate 2 nested RelativeLayout by calling the super() constructor, and the one that is at the root of my XML layout. For that, I know I can instead use <merge> as my XML root and that gets me rid of the extra RelativeLayout. Except that defining XML attributes, such as android:background on my <merge> tag doesn't have any effect, so I have to define it programmatically, which is not as nice.
The custom View is a subclass of RelativeLayout, and therefore expose methods it inherits from it, such as addView(), even if it doesn't make sense to add child views to it. I know I can override those methods to prevent users from doing that, but I would still find it cleaner to inherit from View.

How to extract current Android layout segments into their own custom control?

OK, I have a complete layout built; however, I am not really pleased with the long xml file that has resulted. I have a shorted version of the xml outline and designer view below. And I was wondering how I can abstract out each group of similar components into their own custom control.
For example, in the picture below, I have highlighted one such control that I would like to abstract out. Instead of it being a LinearLayout with 2 TextView's inside with their own properties and attributes set. I would like to reference it via <package-name.individual_song_item
android:layout...> ... </>. All I would have to do is set the first TextView's text along with the second one via attributes in the top-level component.
How can this be done? I have the layout done and complete, but I don't like that nothing is abstracted away.
So the expected results that I am looking for are (if you look at the right-side of the image. there would only be one LinearLayout below the image, and the rest would be <package-name.individual_song_item>)
I have tried to just create a new layout xml with just the subsets of components, but I was not able to make it work when combining it back.
OLD WAY
<LinearLayout >
<ImageView />
<LinearLayout >
<LinearLayout >
<TextView />
<TextView />
</LinearLayout>
<LinearLayout >
<TextView />
<TextView />
</LinearLayout>
<LinearLayout >
<TextView />
<TextView />
</LinearLayout>
....
</LinearLayout>
</LinearLayout>
POSSIBLE PROPOSED WAY
<LinearLayout >
<ImageView />
<LinearLayout >
<com.example.individual_song_item />
<com.example.individual_song_item />
<com.example.individual_song_item />
....
<com.example.individual_song_item <!-- example (possible!?!?) -->
....
app:label="Group"
app:value="Group Name" />
</LinearLayout>
</LinearLayout>
Create a custom layout eg.
public class IndividualSongItem extends LinearLayout {
private String mSong;
private String mSongName;
public IndividualSongItem(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public IndividualSongItem(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.IndividualSongItem);
try {
// Read in your custom layout's attributes,
// for example song and songName text attributes
CharSequence s = a.getString(R.styleable.IndividualSongItem_song);
if (s != null) {
setSong(s.toString());
}
s = a.getString(R.styleable.IndividualSongItem_songName);
if (s != null) {
setSongName(s.toString());
}
} finally {
a.recycle();
}
}
....etc
You will also need to create an attributes XML for your new layout class.
For a full example of how to do what you're after look at the LabelView example in the ApiDemos.
It's also very well explained here.

Android: set view style programmatically

Here's XML:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
style="#style/LightStyle"
android:layout_width="fill_parent"
android:layout_height="55dip"
android:clickable="true"
android:orientation="horizontal" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" />
</RelativeLayout>
How to set style attribute programmatically?
Technically you can apply styles programmatically, with custom views anyway:
private MyRelativeLayout extends RelativeLayout {
public MyRelativeLayout(Context context) {
super(context, null, R.style.LightStyle);
}
}
The one argument constructor is the one used when you instantiate views programmatically.
So chain this constructor to the super that takes a style parameter.
RelativeLayout someLayout = new MyRelativeLayout(new ContextThemeWrapper(this,R.style.RadioButton));
Or as #Dori pointed out simply:
RelativeLayout someLayout = new RelativeLayout(new ContextThemeWrapper(activity,R.style.LightStyle));
Now in Kotlin:
class MyRelativeLayout #JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = R.style.LightStyle,
) : RelativeLayout(context, attributeSet, defStyleAttr)
or
val rl = RelativeLayout(ContextThemeWrapper(activity, R.style.LightStyle))
What worked for me:
Button b = new Button(new ContextThemeWrapper(this, R.style.ButtonText), null, 0);
Use a ContextThemeWrapper
AND
Use the 3-arguments constructor (won't work without this)
Update: At the time of answering this question (mid 2012, API level 14-15), setting the view programmatically was not an option (even though there were some non-trivial workarounds) whereas this has been made possible after the more recent API releases. See #Blundell's answer for details.
OLD Answer:
You cannot set a view's style programmatically yet, but you may find this thread useful.
For a new Button/TextView:
Button mMyButton = new Button(new ContextThemeWrapper(this, R.style.button_disabled), null, 0);
For an existing instance:
mMyButton.setTextAppearance(this, R.style.button_enabled);
For Image or layouts:
Image mMyImage = new ImageView(new ContextThemeWrapper(context, R.style.article_image), null, 0);
This is quite old question but solution that worked for me now is to use 4th parameter of constructor defStyleRes - if available.. on view... to set style
Following works for my purposes (kotlin):
val textView = TextView(context, null, 0, R.style.Headline1)
If you'd like to continue using XML (which the accepted answer doesn't let you do) and set the style after the view has been created you may be able to use the Paris library which supports a subset of all available attributes.
Since you're inflating your view from XML you'd need to specify an id in the layout:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/my_styleable_relative_layout"
style="#style/LightStyle"
...
Then when you need to change the style programmatically, after the layout has been inflated:
// Any way to get the view instance will do
RelativeLayout myView = findViewById(R.id.my_styleable_relative_layout);
// This will apply all the supported attribute values of the style
Paris.style(myView).apply(R.style.LightStyle);
For more: the list of supported view types and attributes (includes background, padding, margin, etc. and can easily be extended) and installation instructions with additional documentation.
Disclaimer: I'm the original author of said library.
You can apply a style to your activity by doing:
super.setTheme( R.style.MyAppTheme );
or Android default:
super.setTheme( android.R.style.Theme );
in your activity, before setContentView().
Non of the provided answers are correct.
You CAN set style programatically.
Short answer is take a look at http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/content/Context.java#435
Long answer.
Here's my snippet to set custom defined style programatically to your view:
1) Create a style in your styles.xml file
<style name="MyStyle">
<item name="customTextColor">#39445B</item>
<item name="customDividerColor">#8D5AA8</item>
</style>
Do not forget to define your custom attributes in attrs.xml file
My attrsl.xml file:
<declare-styleable name="CustomWidget">
<attr name="customTextColor" format="color" />
<attr name="customDividerColor" format="color" />
</declare-styleable>
Notice you can use any name for your styleable (my CustomWidget)
Now lets set the style to the widget Programatically
Here's My simple widget:
public class StyleableWidget extends LinearLayout {
private final StyleLoader styleLoader = new StyleLoader();
private TextView textView;
private View divider;
public StyleableWidget(Context context) {
super(context);
init();
}
private void init() {
inflate(getContext(), R.layout.widget_styleable, this);
textView = (TextView) findViewById(R.id.text_view);
divider = findViewById(R.id.divider);
setOrientation(VERTICAL);
}
protected void apply(StyleLoader.StyleAttrs styleAttrs) {
textView.setTextColor(styleAttrs.textColor);
divider.setBackgroundColor(styleAttrs.dividerColor);
}
public void setStyle(#StyleRes int style) {
apply(styleLoader.load(getContext(), style));
}
}
layout:
<TextView
android:id="#+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="22sp"
android:layout_gravity="center"
android:text="#string/styleble_title" />
<View
android:id="#+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"/>
</merge>
And finally StyleLoader class implementation
public class StyleLoader {
public StyleLoader() {
}
public static class StyleAttrs {
public int textColor;
public int dividerColor;
}
public StyleAttrs load(Context context, #StyleRes int styleResId) {
final TypedArray styledAttributes = context.obtainStyledAttributes(styleResId, R.styleable.CustomWidget);
return load(styledAttributes);
}
#NonNull
private StyleAttrs load(TypedArray styledAttributes) {
StyleAttrs styleAttrs = new StyleAttrs();
try {
styleAttrs.textColor = styledAttributes.getColor(R.styleable.CustomWidget_customTextColor, 0);
styleAttrs.dividerColor = styledAttributes.getColor(R.styleable.CustomWidget_customDividerColor, 0);
} finally {
styledAttributes.recycle();
}
return styleAttrs;
}
}
You can find fully working example at https://github.com/Defuera/SetStylableProgramatically
This is my simple example, the key is the ContextThemeWrapper wrapper, without it, my style does not work, and using the three parameters constructor of the View.
ContextThemeWrapper themeContext = new ContextThemeWrapper(this, R.style.DefaultLabelStyle);
TextView tv = new TextView(themeContext, null, 0);
tv.setText("blah blah ...");
layout.addView(tv);
the simple way is passing through constructor
RadioButton radioButton = new RadioButton(this,null,R.style.radiobutton_material_quiz);
I don't propose to use ContextThemeWrapper as it do this:
The specified theme will be applied on top of
the base context's theme.
What can make unwanted results in your application. Instead I propose new library "paris" for this from engineers at Airbnb:
https://github.com/airbnb/paris
Define and apply styles to Android views programmatically.
But after some time of using it I found out it's actually quite limited and I stopped using it because it does not support a lot of properties i need out off the box, so one have to check out and decide as always.
int buttonStyle = R.style.your_button_style;
Button button = new Button(new ContextThemeWrapper(context, buttonStyle), null, buttonStyle);
Only this answer works for me. See https://stackoverflow.com/a/24438579/5093308
best simple solution i found, using alertDialog with a custom layout, is :
val mView = LayoutInflater.from(context).inflate(layoutResId, null)
val dialog = AlertDialog.Builder(context, R.style.CustomAlertDialog)
.setView(mView)
.setCancelable(false)
.create()
where style is
<style name="CustomAlertDialog" parent="Theme.AppCompat.Light.Dialog.Alert">
<item name="android:background">#drawable/bg_dialog_white_rounded</item>
</style>
and bg_dialog_white_rounded.xml is
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="16dp" />
<solid android:color="#Color/white" />
</shape>
layoutResId is a resource id of any layout that has to have the theme set to "#style/CustomAlertDialog", for example:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="#dimen/wdd_margin_medium"
android:theme="#style/CustomAlertDialog"
android:layout_marginEnd="#dimen/wdd_margin_medium">
..... etc...
</androidx.constraintlayout.widget.ConstraintLayout>
I used views defined in XML in my composite ViewGroup, inflated them added to Viewgroup. This way I cannot dynamically change style but I can make some style customizations. My composite:
public class CalendarView extends LinearLayout {
private GridView mCalendarGrid;
private LinearLayout mActiveCalendars;
private CalendarAdapter calendarAdapter;
public CalendarView(Context context) {
super(context);
}
public CalendarView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onFinishInflate() {
super.onFinishInflate();
init();
}
private void init() {
mCalendarGrid = (GridView) findViewById(R.id.calendarContents);
mCalendarGrid.setNumColumns(CalendarAdapter.NUM_COLS);
calendarAdapter = new CalendarAdapter(getContext());
mCalendarGrid.setAdapter(calendarAdapter);
mActiveCalendars = (LinearLayout) findViewById(R.id.calendarFooter);
}
}
and my view in xml where i can assign styles:
<com.mfitbs.android.calendar.CalendarView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/calendar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="vertical"
>
<GridView
android:id="#+id/calendarContents"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:id="#+id/calendarFooter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
/>
if inside own custom view :
val editText = TextInputEditText(context, attrs, defStyleAttr)
You can create the xml containing the layout with the desired style and then change the background resource of your view, like this.

How to create reusable xml wrappers for Android Layout files

I have several layout files that are mostly the same, except for one section. Is there a way that I can have the common XML all in one place; instead of copy/pasting, and having to update a bunch of files when I want to make 1 change?
I know that I can include XML from other XML files, but the common code isn't an internal control; it is the outer wrapper; so include doesn't work. Basically, I have a bunch of files that all look like this:
<LinearLayout
android:id="#+id/row"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<ImageView android:layout_height="26dp"
android:id="#+id/checkImage"
android:layout_width="26dp"
android:layout_alignParentTop="true"
android:scaleType="fitCenter"/>
<!-- Different types of views go here depending on which layout file it is -->
<ImageButton android:layout_height="fill_parent"
android:id="#+id/playButton"
android:layout_width="42dp"
android:src="#drawable/play_button"
android:scaleType="center"
android:background="#00000000"/>
</LinearLayout>
Basically, I want to do what ASP.Net does with Master Pages. Is there any option for this?
The solution was pretty easy.
You need to extend "Activity" Class, in onCreate() function SetContentView to your base xml layout and also need to override setContentView in base Activity Class
For Example:
1.Create base_layout.xml with the below code
<?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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="#+id/image_view_01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxHeight="50dp" />
</LinearLayout>
<LinearLayout
android:id="#+id/base_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</LinearLayout>
</LinearLayout>
Create BaseActivity.java
public class BaseActivity extends Activity {
ImageView image;
LinearLayout baseLayout;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.base_layout);
this.image = (ImageView) this.findViewById(R.id.image_view_01);
this.baseLayout = (LinearLayout) this.findViewById(R.id.base_layout);
this.image.setImageResource(R.drawable.header);
}
#Override
public void setContentView(int id) {
LayoutInflater inflater = (LayoutInflater)getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(id, this.baseLayout);
}
}
and SomeActivity.java
public class SomeActivity extends BaseActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.some_layout);
//rest of code
}
}
The only thing I noticed so far was that when requesting a progress bar (requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS)) this needs to be done before calling super.onCreate. I think this is because nothing can be drawn yet before calling this function.
This worked great for me and hopefully you will find this useful in your own coding.
Maybe you could use one main layout XML file and then add/remove other widgets dynamically through code as needed.
I was trying to do exactly this - I wanted a view that had a button on the left and a button on the right, but could have arbitrary content in the middle (depending on who was including it). Basically a custom view group that could have child view in the XML layout, and would wrap those child views with another XML layout. Here is how I did it:
top_bar.xml: This represents the common layout to wrap things with. Note the LinearLayout (could be any layout) with an ID "addChildrenHere" - it is referenced later.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/topBarLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<Button
android:id="#+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="left" />
<LinearLayout
android:id="#+id/addChildrenHere"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Button
android:id="#+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="right" />
</LinearLayout>
main.xml: The main layout. This includes a custom viewgroup (WrappedLayout) with a few children. Note how it declares a custom XML namespace, and sets two custom attributes on the WrappedLayout tag (these say which layout to wrap the children with, and where within that layout the children of this node should be placed).
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:karl="http://schemas.android.com/apk/res/karl.test"
android:id="#+id/linearLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<karl.test.WrappedLayout
android:id="#+id/topBarLayout1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
karl:layoutToInflate="#layout/top_bar"
karl:childContainerID="#+id/addChildrenHere">
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is a child of the special wrapper."
android:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
android:id="#+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is another child; you can put anything here."
android:textAppearance="?android:attr/textAppearanceMedium" />
</karl.test.WrappedLayout>
</LinearLayout>
attrs.xml: This goes in res/values. This defines the custom XML attributes used in the XML above.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="WrappedLayout">
<attr name="layoutToInflate" format="integer"/>
<attr name="childContainerID" format="integer"/>
</declare-styleable>
</resources>
Finally, WrappedLayout.java: This handles reading the custom attributes, and doing a bit of hackery to make addView() actually add the views in a different place.
package karl.test;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
public class WrappedLayout extends FrameLayout
{
///Attempts to add children to this layout will actually get forwarded through to mChildContainer.
///This would be final, but it's actually used indirectly by the constructor before it's initialised.
private ViewGroup mChildContainer;
public WrappedLayout(Context context, AttributeSet attrs)
{
super(context, attrs);
//read the custom attributes
final int layoutToInflate;
final int childContainerID;
{
final TypedArray styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.WrappedLayout);
layoutToInflate = styledAttributes.getResourceId(R.styleable.WrappedLayout_layoutToInflate, 0);
childContainerID = styledAttributes.getResourceId(R.styleable.WrappedLayout_childContainerID, 0);
styledAttributes.recycle();
}
if(layoutToInflate == 0
|| childContainerID == 0)
{
Log.e("Error", "WrappedLayout.WrappedLayout(): Error reading custom attributes from XML. layoutToInflate = " + layoutToInflate + ", childContainerID =" + childContainerID);
}
else
{
//inflate the layout and (implicitly) add it as a child view
final View inflatedLayout = View.inflate(context, layoutToInflate, this);
//grab the reference to the container to pass children through to
mChildContainer = (ViewGroup)inflatedLayout.findViewById(childContainerID);
}
}
///All the addView() overloads eventually call this method.
#Override
public void addView(View child, int index, ViewGroup.LayoutParams params)
{
if(mChildContainer == null)
{
//still inflating - we're adding one of the views that makes up the wrapper structure
super.addView(child, index, params);
}
else
{
//finished inflating - forward the view through to the child container
mChildContainer.addView(child, index, params);
}
}
}
This works, as far as I can tell. It doesn't work very well with the Eclipse layout editor (I'm not quite sure what the problem is), but you can view the layout fine. Changing the children of the WrappedLayout seems to require editing the XML manually.
Have you looked at Applying Styles and Themes?

Categories

Resources