Layout editor does not pass styleable attribute to custom View class - android

I am creating a FixedAspectImageView to solve the problem in Android of making an ImageView set its view bounds to match the aspect ratio of the image contained within, and also fill an arbitrarily wide (or tall) available area and stretch the image appropriately. In the current version I'm sending a floating point "ratio" parameter from the layout, though eventually I would intend to detect the ratio of the image.
However, I am having trouble with the styleable attributes. My attrs.xml has this:
<resources>
<!-- other stuff, which bizarrely works -->
<declare-styleable name="FixedAspectImageView">
<attr name="ratio" format="float"/>
</declare-styleable>
</resources>
My constructor looks like:
public FixedAspectImageView(Context context, AttributeSet attrs) {
super(context, attrs);
if(attrs!=null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FixedAspectImageView);
if(a.hasValue(R.styleable.FixedAspectImageView_ratio)) {
setRatio(a.getFloat(R.styleable.FixedAspectImageView_ratio, 1.f));
}
a.recycle();
}
}
(and very similarly for the one with an integer defStyle at the end) and my layout file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:mypackage="http://schemas.android.com/apk/res/com.my.package"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<com.my.package.view.FixedAspectImageView
android:id="#+id/fixedAspectImageView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitXY"
android:src="#drawable/picture_of_goatse"
mypackage:ratio="1.2329" />
</FrameLayout>
</LinearLayout>
I've determined that setRatio(Float) works, because if I set a value in the source the measurement system works perfectly. The TypedArray being passed in has no indices set. What's causing this?

The layout editor does not always pick up <declare-styleable> attributes straight away. Restarting eclipse fixed the problem. The above code was all correct; the package name should obviously be the package of the project for the namespace in the layout, and the custom View will use generated values from R.styleable.
Known issue:
https://code.google.com/p/android/issues/detail?id=52298

Related

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>

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.

Can't load custom attributes in XML

I tried to follow this guide and this post on how to make custom attributes for the layout XML of a custom View, however no matter what I do, I get back a list of zero custom attributes, and I am having a lot of trouble figuring out how to debug this.
I made a file res/values/attrs.xml like so:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="TexturedListView">
<attr name="backgroundTexture" format="reference"/>
</declare-styleable>
</resources>
I then created a custom view within a layout XML file like so:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="https://schemas.android.com/apk/res/xxx.rtin.texturedlistview"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<xxx.rtin.texturedlistview.TexturedListView
android:id="#+id/texturedListView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:dividerHeight="10dp"
android:padding="20dp"
custom:backgroundTexture="#drawable/background" >
</xxx.rtin.texturedlistview.TexturedListView>
</RelativeLayout>
Then from within the constructor of my TexturedListView class, I call the following attempting to read this custom attribute:
public TexturedListView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(R.styleable.TexturedListView);
final int N = a.getIndexCount();
//..snip...
}
Without fail, however, N is 0. R.styleable.TexturedListView seems to have proper ID in it, and I've tried cleaning and rebuilding my project, but to no avail. I feel like I have to be missing something subtle, but I can't figure it out.
What am I doing wrong? How do I read these custom attributes?

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.

How to create a simple custom View?

I would like to create an custom View on Android. I have tried to do it as simple as possible and created an almost empty class MyView and used it in my LinearLayout but the application fails on start with "Force Close". How can I do a simple custom View? According to Building Custom Components the View gets the size 100x100 if I don't override onMeasure().
public class MyView extends View {
public MyView(Context context) {
super(context);
}
}
And I use it in a LinearLayout with:
<view
class="com.example.MyView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0.0" />
What am I doing wrong?
If I use the constructor that itemon suggest and the corresponding call to the superclass. Then the "Force Close" is gone, but my LinearLayout is broken, the components after MyView isn't shown.
Here is my main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0.0"
android:background="#f00"
android:text="Hello"
/>
<view
class="com.example.MyView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0.0"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0.0"
android:background="#00f"
android:text="World"
/>
</LinearLayout>
may be you could define another constructor method like this:
public MyView(Context context, AttributeSet attrs)
the android framework will try to build the UI with your view from the constructor above.
The Android Developer Guide has a section called Building Custom Components. Unfortunately, the discussion of XML attributes only covers declaring the control inside the layout file and not actually handling the values inside the class initialisation. The steps are as follows:
Declare attributes in values\attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyCustomView">
<attr name="android:text"/>
<attr name="android:textColor"/>
<attr name="extraInformation" format="string" />
</declare-styleable>
</resources>
Notice the use of an unqualified name in the declare-styleable tag. Non-standard android attributes like extraInformation need to have their type declared. Tags declared in the superclass will be available in subclasses without having to be redeclared.
Create constructors
Since there are two constructors that use an AttributeSet for initialisation, it is convenient to create a separate initialisation method for the constructors to call.
private void init(AttributeSet attrs){
TypedArray a=getContext().obtainStyledAttributes(attrs,R.styleable.MyCustomView);
//Use a
Log.i("test",a.getString(R.styleable.MyCustomView_android_text));
Log.i("test",""+a.getColor(R.styleable.MyCustomView_android_textColor, Color.BLACK));
Log.i("test",a.getString(R.styleable.MyCustomView_android_extraInformation));
//Don't forget this
a.recycle();
}
R.styleable.MyCustomView is an autogenerated int[] resource where each element is the ID of an attribute. Attributes are generated for each property in the XML by appending the attribute name to the element name. Attributes can then be retrieved from the TypedArray using various get functions. If the attribute is not defined in the XML, then null is returned. Except, of course, if the return type is a primitive, in which case the second argument is returned.
If you don't want to retrieve all of the attributes, it is possible to create this array manually.The ID for standard android attributes are included in android.R.attr, while attributes for this project are in R.attr.
int attrsWanted[]=new int[]{android.R.attr.text, R.attr.textColor};
Please note that you should not use anything in android.R.styleable, as per this thread it may change in the future. It is still in the documentation as being to view all these constants in the one place is useful.
Use it in a layout files such as layout\main.xml
Include the namespace declaration
xmlns:app="http://schemas.android.com/apk/res/com.mycompany.projectname"
in the top level xml element.
<com.mycompany.projectname.MyCustomView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:text="Test text"
android:textColor="#FFFFFF"
app:extraInformation="My extra information";
/>
Reference the custom view using the fully qualified name.
Android LabelView Sample
If you want a complete example, look at the android label view sample.
LabelView.java
TypedArray a=context.obtainStyledAttributes(attrs, R.styleable.LabelView);
CharSequences=a.getString(R.styleable.LabelView_text);
attrs.xml
<declare-styleable name="LabelView">
<attr name="text"format="string"/>
<attr name="textColor"format="color"/>
<attr name="textSize"format="dimension"/>
</declare-styleable>
custom_view_1.xml
<com.example.android.apis.view.LabelView
android:background="#drawable/blue"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
app:text="Blue"app:textSize="20dp"/>
This is contained in a LinearLayout with a namespace attribute:
xmlns:app="http://schemas.android.com/apk/res/com.example.android.apis"

Categories

Resources