Seaching pattern to remove not needed layouts for viewgroup subclasses - android

Given I want some subclassing of a LinearLayout like this:
public class MyLayout extends LinearLayout {
public int someState=0;
public ListViewHeadView(Context context) {
super(context);
View.inflate(context, R.layout.some_layout, this);
}
// more code here
}
with some_layout like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
style="#style/textStyle"
android:id="#+id/someTextView"/>
<Button
style="#style/buttonStyle"
android:id="#+id/someButton"/>
</LinearLayout>
this works fine, but I have one LinearLayout to much which costs performance. The only way I see at the moment to remove the unneeded LinearLayout is to use a <merge> inside the xml instead of the LinearLayout. But then I would have to do a setOrientation in my code which I would love to see in an xml ( once - not at all usages ).
Can somebody point me to a pattern on how to do this nicely? If I do not have a state I can use the decorator-pattern and just decorate the LinearLayout with the functions I need - but when I need a state in this Layout I see no good way yet. Any hints and best practices welcome!

I'd define the android:orientation attribute in your project's attrs.xml for your custom ViewGroup and add some code to evaluate this in the ViewGroups constructor.

This is one of the uses of the <merge> element. Put it as the root in your XML file in place of the <LinearLayout>
The orientation can then be set whenever you use your custom view using <com.example.MyView ... android:orientation="....">
Or in code with setOrientation
If the orientation is always the same for this customs view, you can override set orientation and not call the super or something to that effect

Define a stylable attribute and provide the orientation through the style
attr.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyLayout">
<attr name="myLayoutStyle" format="reference"/>
</declare-styleable>
</resources>
style.xml
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
<item name="android:windowBackground">#color/main_background</item>
<item name="myLayoutStyle">#style/MyLayoutStyle</item>
</style>
<style name="MyLayoutStyle" parent="android:Widget.Holo.Light">
<item name="android:orientation">vertical</item>
</style>
</resources>
Then in your constructor:
public class MyLayout extends LinearLayout {
public int someState=0;
public ListViewHeadView(Context context) {
super(context, null, R.attr.MyLayoutStyle)
View.inflate(context, R.layout.some_layout, this);
}
// more code here
}
In the constructor the reference is to the Stylable, not the style
Solution adapted from:
http://trickyandroid.com/protip-inflating-layout-for-your-custom-view/

Related

Views created progmatically aren't inheriting theme

I am trying to create a view pragmatically and then add it to my activity. This bit is working fine, however the theme for the view group isn't inherited by my new view
My theme:
<style name="CustomButtonTheme" parent="#style/Widget.AppCompat.Button">
<item name="android:textColor">#FF0000</item>
<item name="android:background">#00FF00</item>
</style>
My layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/buttonArea"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:theme="#style/CustomButtonTheme">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This button inherits CustomButtonTheme" />
</LinearLayout>
Java code
AppCompatButton button = new AppCompatButton(getContext());
button.setText("This button does not inherit CustomButtonTheme");
LinearLayout buttonArea = findViewById<LinearLayout>(R.id.buttonArea);
buttonArea.addView(button);
An android:theme attribute in a layout will only have effect during inflation, and only on that particular subtree. It won't be applied to the Activity's overall theme.
All that attribute does, though, is cause the LayoutInflater to wrap its current Context with the specified theme in a ContextThemeWrapper. We could do something similar ourselves, and just to illustrate the basic usage:
ContextThemeWrapper wrapper = new ContextThemeWrapper(getContext(), R.style.CustomButtonTheme);
AppCompatButton button = new AppCompatButton(wrapper);
However, this has already been done for us, basically, when the LayoutInflater created a ContextThemeWrapper internally, for that android:theme attribute. That ContextThemeWrapper is the Context that the LinearLayout will have been created with, so we can simply use its Context to instantiate our AppCompatButton:
AppCompatButton button = new AppCompatButton(buttonArea.getContext());
As the OP points out, this has the added benefit of working in pretty much every similar setup without having to know the exact theme needed.

Adding theme to Seekbar programmatically makes Seekbar disappear

I need to add a variable number of Seekbars depending on configuration.
for(int i=0;i<length;i++){
seeks[i] = new SeekBar(getActivity());
seeks[i].setMax(4);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(30,30,30,30);
seeks[i].setLayoutParams(layoutParams);
ll.addView(seeks[i]);
seeks[i].setVisibility(View.VISIBLE);
Works perfectly, but I would like the Seekbar to be discrete. In XML you can just add:
<SeekBar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/Base.Widget.AppCompat.SeekBar.Discrete"/>
However, if I follow the Docs, you should be able to add a Theme in the Seekbar constructor like so:
seeks[i] = new SeekBar(getActivity(), null, R.style.myTheme2 );
where I have set the theme in the styles.xml, OR
seeks[i] = new SeekBar(getActivity(), null, R.style.Widget_AppCompat_SeekBar_Discrete);
However, as soon as I do either of those things, the Seekbar disappears from view completely.
Got it working, leaving up for others who have the same problem, as I couldn't find it anywhere:
First setup a layout for each individual SeekBar:
seekbar_base.xml
<?xml version="1.0" encoding="utf-8"?>
<SeekBar
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:progressBackgroundTint="#color/seekBarBackground"
android:progressTint="#color/seekBarProgress"
android:thumbTint="#color/seekBar"
android:paddingLeft="20sp"
android:paddingTop="3dp"
android:paddingRight="20dp"
android:paddingBottom="30dp"
android:theme="#style/Widget.AppCompat.SeekBar.Discrete" />
Then instantiate it like so:
SeekBar bar = (SeekBar)LayoutInflater.from(getActivity()).inflate(R.layout.seekbar_base, null);
I still have no idea why it didn't work the way the docs said it should, but oh well.
when you use the XML, it actually set some default attribute. it is located in core / res / res / values / styles.xml
<style name="Widget.SeekBar">
<item name="android:indeterminateOnly">false</item>
<item name="android:progressDrawable">#android:drawable/progress_horizontal</item>
<item name="android:indeterminateDrawable">#android:drawable/progress_horizontal</item>
<item name="android:minHeight">20dip</item>
<item name="android:maxHeight">20dip</item>
<item name="android:thumb">#android:drawable/seek_thumb</item>
<item name="android:thumbOffset">8dip</item>
<item name="android:focusable">true</item>
</style>
at the same time, the SeekBar(Context context) also use the defalut style. it relaize that by call the same method you called, but set a defalut style.
public SeekBar(Context context) {
this(context, null);
}
public SeekBar(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.seekBarStyle);
}
so change your theme extends it.the add your custom item.
seeks[i] = new SeekBar(getActivity(), null, R.style.Widget_AppCompat_SeekBar_Discrete);
here are the document descirbe for three params constructor:
Perform inflation from XML and apply a class-specific base style from a
theme attribute. This constructor of View allows subclasses to use their
own base style when they are inflating. For example, a Button class's
constructor would call this version of the super class constructor and
supply R.attr.buttonStyle for defStyleAttr; this
allows the theme's button style to modify all of the base view attributes
(in particular its background) as well as the Button class's attributes.

Android layout resources for Accessibility vision impairment

Android applications currently support different layout resources based on orientation, screen size, day and night etc.
However, I would like to provide layouts targeted at users with vision impairments, for instance use layouts with YELLOW background and BLACK text.
Have I missed something that Android already supports?
Can I implement custom res/layout-WAI or res/layout-DDA folders?
You can't create custom configuration qualifiers.
The current supported qualifiers are listed here.
I will suggest the following workaround (Example):
Create a special layout for WAI for each existing layout, with the same name, but with the suffix "_wai"
example_layout.xml
example_layout_wai.xml
Create a method to resolve the appropriate layout based on system needs. Say we have a method isWAI(), resolve method will look something like:
public int resolveLayoutResourceID(int layoutResID) {
int newLayoutResID = layoutResID;
if (isWAI()) {
String layoutResName = getResources().getResourceEntryName(layoutResID);
String newLayoutResName = layoutResName + "_wai";
newLayoutResID = getResources().getIdentifier(newLayoutResName, "layout", getPackageName());
}
return newLayoutResID;
}
Create a BaseActivity class for all your classes (or use a utility static function), that will override the setContentView method. There you will add a logic to select the layout.
#Override
public void setContentView(int layoutResID) {
int newLayoutResID = resolveLayoutResourceID(layoutResID)
super.setContentView(newLayoutResID);
}
Color (YELLOW background and BLACK text), fonts and font size is a topic for Styles and Themes.
Unless visually imapaired people need own layouts (arrangement, ordering of gui elements) you can implement a setting with a style chooser that can be applied to every layout
Instead of providing two different layouts, you can parametrize views in a single layout. Thus, views of your layout would take parameters (e.g. background color, text color) from the context theme they are inflated in.
So, this is what we want to achieve:
<android.support.constraint.ConstraintLayout
android:background="?attr/bgColor"
... >
<TextView
android:textColor="?attr/textColor"
... />
</android.support.constraint.ConstraintLayout>
?attr/someAttribute would be taken from the theme of the current context.
Create attributes at attrs.xml in values/:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyAttrs">
<attr name="bgColor" format="reference|color"/>
<attr name="textColor" format="color"/>
</declare-styleable>
</resources>
In styles.xml declaring two themes extending from a common theme:
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
...
</style>
<style name="AppTheme.Default">
<item name="bgColor">#color/red</item>
<item name="textColor">#color/blue</item>
</style>
<style name="AppTheme.Accessibility">
<item name="bgColor">#color/orange</item>
<item name="textColor">#color/yellow</item>
</style>
</resources>
Then, in your activity perform the assertion and set a correct theme:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
setTheme(isAccessibility ? R.style.AppTheme_Accessibility : R.style.AppTheme_Default);
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
...
}
Or, if you have to do it on runtime, you may use ContextThemeWrapper to inflate specific view with appropriate theme.
Context wrapper = new ContextThemeWrapper(MyFragment.this.getContext(), R.style.AppTheme_Accessibility);
// inflating with a `wrapper`, not with the activity's theme
View themedView = View.inflate(wrapper, R.layout.some_layout, parent);
This is much better approach then providing two separate layouts, because it refrains you from maintaining two layouts when a change happens in UI.

Android how to use app attribute for custom class options [duplicate]

I know it is possible to create custom UI element (by way of View or specific UI element extension). But is it possible to define new properties or attributes to newly created UI elements (I mean not inherited, but brand new to define some specific behavior I am not able to handle with default propertis or attributes)
e.g. element my custom element:
<com.tryout.myCustomElement
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Element..."
android:myCustomValue=<someValue>
/>
So is it possible to define MyCustomValue?
Thx
Yes. Short guide:
Create an attribute XML
Create a new XML file inside /res/values/attrs.xml, with the attribute and its type
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<declare-styleable name="MyCustomElement">
<attr name="distanceExample" format="dimension"/>
</declare-styleable>
</resources>
Basically you have to set up one <declare-styleable /> for your view that contains all your custom attributes (here just one). I never found a full list of possible types, so you need to look at the source for one I guess. Types that I know are reference (to another resource), color, boolean, dimension, float, integer and string. They are pretty self-explanatory
Use the attributes in your layout
That works the same way you did above, with one exception. Your custom attribute needs its own XML namespace.
<com.example.yourpackage.MyCustomElement
xmlns:customNS="http://schemas.android.com/apk/res/com.example.yourpackage"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Element..."
customNS:distanceExample="12dp"
/>
Pretty straight forward.
Make use of the values you get passed
Modify the constructor of your custom view to parse the values.
public MyCustomElement(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MyCustomElement, 0, 0);
try {
distanceExample = ta.getDimension(R.styleable.MyCustomElement_distanceExample, 100.0f);
} finally {
ta.recycle();
}
// ...
}
distanceExample is a private member variable in this example. TypedArray has lots of other things to parse other types of values.
And that's it. Use the parsed value in your View to modify it, e.g. use it in onDraw() to change the look accordingly.
In your res/values folder create attr.xml. There you can define your attribues:
<declare-styleable name="">
<attr name="myCustomValue" format="integer/boolean/whatever" />
</declare-styleable>
When you then want to use it in your layout file you have to add
xmlns:customname="http://schemas.android.com/apk/res/your.package.name"
and then you can use the value with customname:myCustomValue=""
Yes , you can.Just use <resource> tag.
like this:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="CodeFont" parent="#android:style/TextAppearance.Medium">
<item name="android:layout_width">fill_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textColor">#00FF00</item>
<item name="android:typeface">monospace</item>
</style>
</resources>
link from official website

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>

Categories

Resources