Using custom states, onCreateDrawableState never called - android

I have an ImageView subclass with custom states. onCreateDrawableState is not called when the widget is instantiated and the image graphic does not appear in my layout. Even if I call refreshDrawableState(), it does not work. I single stepped through the latter and the View code is expecting m_background to be already set (it's still null in my case).
What am I missing that would cause m_background to have an initial value?
values/attrs.xml
<resources>
<declare-styleable
name="toggle_states">
<attr name="state_left" format="boolean"/>
<attr name="state_right" format="boolean"/>
</declare-styleable>
</resources>
drawables/selector_toggle.xml
<selector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:myapp="http://schemas.android.com/apk/res/com.example.test" >
<item
myapp:state_left="true"
android:drawable="#drawable/toggle_left" />
<item
myapp:state_right="true"
android:drawable="#drawable/toggle_right" />
</selector>
Toggle.java
public class Toggle extends ImageView
{
private static final int[] STATE_LEFT = {R.attr.state_left};
private static final int[] STATE_RIGHT= {R.attr.state_right};
public enum State {LEFT, RIGHT};
private State state = State.LEFT;
public Toggle (Context context, AttributeSet attrs)
{
super(context, attrs);
}
#Override
public int[] onCreateDrawableState (int extraSpace)
{
final int[] drawableState = super.onCreateDrawableState (extraSpace + 1);
if (state == State.LEFT)
mergeDrawableStates (drawableState, STATE_LEFT);
else
mergeDrawableStates (drawableState, STATE_RIGHT);
return drawableState;
}
}
some_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:myapp="http://schemas.android.com/apk/res/com.example.test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
>
...
<com.example.test.Toggle
android:id="#+id/toggle"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
myapp:state_left="true"
/>
...
</LinearLayout>

You've missed android:background="#drawable/selector_toggle.xml" in com.example.test.Toggle
<com.example.test.Toggle
android:id="#+id/toggle"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:background="#drawable/selector_toggle.xml"
myapp:state_left="true"
/>

Related

Custom component not rendering properly anymore

I'm new to creating custom components on android and it was a fun, annoying, and very educational experience. I was able to make my quite complex custom component and I'm very happy with it. But after I moved it to a directory and out of it, it doesn't display anything anymore.
My project consist of a lot of fragments and my custom component is used on one of them. So when I moved all those fragments in a directory, AS told me that someone can't find something on my custom component class. So I included the custom component class inside the fragments directory. Everything worked fine but when I tried to use the custom view on a different layout which is not a fragment's, the custom component doesn't display anything. I made every variable of the custom view class to public then moved it outside the fragments folder. Now, it doesn't display anything anymore. Please point out what I've done wrong.
This is custom component class.
public class CheckOutItem extends ConstraintLayout {
public Context ctx;
public Paint mPaint;
public Rect mRect;
int mSquareColor;
public ImageView imgThumbnail;
public TextView lblAbbrev, lblFullName;
public ConstraintLayout lytMain;
public CheckOutItem(Context context) {
super(context);
ctx = context;
init(null);
}
public CheckOutItem(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.checkout_item, this);
ctx = context;
init(attrs);
}
public CheckOutItem(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ctx = context;
init(attrs);
}
private void init(#Nullable AttributeSet set){
inflate(ctx, R.layout.checkout_item, this);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mRect = new Rect();
if(set == null){
return;
}
TypedArray ta = getContext().obtainStyledAttributes(set, R.styleable.CheckOutItem);
this.imgThumbnail = findViewById(R.id.imgItemThumbnail);
this.lblAbbrev = findViewById(R.id.lblItemAbbrevName);
this.lblFullName = findViewById(R.id.lblItemFullName);
this.lytMain = findViewById(R.id.lytMain);
this.lblAbbrev.setText(ta.getText(R.styleable.CheckOutItem_abbrevText));
this.lblAbbrev.setTextSize(ta.getDimension(R.styleable.CheckOutItem_abbrevTextSize, 1f));
this.lblAbbrev.setTextColor(ta.getColor(R.styleable.CheckOutItem_abbrevTextColor, Color.BLACK));
this.lblFullName.setText(ta.getText(R.styleable.CheckOutItem_fullNameText));
this.lblFullName.setTextSize(ta.getDimension(R.styleable.CheckOutItem_fullNameTextSize, 1f));
this.lblFullName.setTextColor(ta.getColor(R.styleable.CheckOutItem_fullNameTextColor, Color.BLACK));
this.lblFullName.setBackgroundColor(ta.getColor(R.styleable.CheckOutItem_fullNameBackgroundColor, Color.WHITE));
this.lytMain.setBackgroundColor(ta.getColor(R.styleable.CheckOutItem_mainBackgroundColor, Color.LTGRAY));
ta.recycle();
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mRect.left = 0;
mRect.right = getWidth();
mRect.top = 0;
mRect.bottom = getHeight();
canvas.drawRect(mRect, mPaint);
}
}
This is my layout.
<?xml version="1.0" encoding="utf-8"?>
<merge 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.support.constraint.ConstraintLayout
android:id="#+id/lytMain"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/border_bg">
<ImageView
android:id="#+id/imgItemThumbnail"
android:layout_width="match_parent"
android:layout_height="70dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="#tools:sample/avatars[9]"
tools:visibility="gone" />
<TextView
android:id="#+id/lblItemAbbrevName"
android:layout_width="0dp"
android:layout_height="60dp"
android:gravity="center"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/lblItemFullName"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="1dp"
android:layout_marginEnd="1dp"
android:gravity="center"
android:textAlignment="center"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/lblItemAbbrevName"
app:layout_constraintVertical_bias="0.0" />
</android.support.constraint.ConstraintLayout>
</merge>
This is my attrs.xml.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CheckOutItem">
<attr name="thumbnail" format="string"/>
<attr name="abbrevText" format="string"/>
<attr name="abbrevTextSize" format="dimension"/>
<attr name="abbrevTextColor" format="color|reference"/>
<attr name="fullNameText" format="string"/>
<attr name="fullNameTextSize" format="dimension"/>
<attr name="fullNameTextColor" format="color|reference"/>
<attr name="textStyle">
<enum name="normal" value="0"/>
<enum name="bold" value="1"/>
<enum name="italic" value="2"/>
</attr>
<attr name="fullNameBackgroundColor" format="color|reference"/>
<attr name="mainBackgroundColor" format="color|reference"/>
</declare-styleable>
</resources>
This is how I use it.
<com.example.android.projectname.CheckOutItem
android:id="#+id/coi2"
android:layout_width="102dp"
android:layout_height="102dp"
android:layout_margin="7.8dp"
app:abbrevText="MP"
app:abbrevTextColor="#color/black"
app:abbrevTextSize="12sp"
app:fullNameBackgroundColor="#color/colorAccent"
app:fullNameText="Make Payment"
app:fullNameTextColor="#color/white"
app:fullNameTextSize="8sp"
app:mainBackgroundColor="#color/transparent"
app:thumbnail="" />
You are inflating your layout twice
public CheckOutItem(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.checkout_item, this);
...
Remove the second inflate in init
private void init(#Nullable AttributeSet set){
// inflate(ctx, R.layout.checkout_item, this); // remove this line
...

Custom controls Android

I have passed few online courses on Android. And started my first project. Currently i'm stuck on creating my own Widget.
I cannot find any information on how to override default control styles or how to group default controls into custom control. The problem is:
When we create extend an Activity there is a method onCreated where we point which XML layout to use. In my opinion in the same way custom controls should be created.
For example, i want to create a control, that will have ImageView and some buttons, which type will depend on data coming from the server ( email button, skype button or any other )
So i created a class:
public class InteractiveHeaderControl extends View {
public InteractiveHeaderControl(Context context)
{
super(context);
}
}
I created a layout:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="TEST"
android:layout_marginLeft="20dp"/>
<LinearLayout
android:id="#+id/button_stack"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:orientation="horizontal"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true">
<Button
android:layout_width="50dp"
android:layout_height="50dp" />
<Button
android:layout_width="50dp"
android:layout_height="50dp" />
<Button
android:layout_width="50dp"
android:layout_height="50dp" />
</LinearLayout>
</RelativeLayout>
So the question is, how can i apply this layout to my control and poulate the button_stack in runtime?
For example in code i want to create this control, like:
InteractiveHeaderControl control = new InteractiveHeaderControl(List<button>);
and in control constructor handle the passed parameter.
Use a Layout instead of a view. Here is a example:
public class DefaultHeading extends FrameLayout {
public DefaultHeading(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = mInflater.inflate(R.layout.default_heading, this, true);
addView(view);
}
And if you want to use some attributes:
Add this to res->values->attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="awesomeedit">
<attr name="regex" format="string"/>
<attr name="name" format="string"/>
<attr name="hint" format="string"/>
<attr name="error" format="string"/>
<attr name="required" format="boolean"/>
<attr name="link" format="string"/>
<attr name="email" format="boolean"/>
<attr name="number" format="boolean"/>
<attr name="password" format="boolean"/>
</declare-styleable>
</resources>
use it in your layout file:
<yourpackage.DefaultHeading
...
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:error="#string/not_valid_email_msg"
...
>
Get values:
public class DefaultHeading extends FrameLayout {
public DefaultHeading(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.awesomeedit, 0, 0);
String name;
String regex;
String hint;
String error;
String link;
try {
name = ta.getString(R.styleable.awesomeedit_name);
regex = ta.getString(R.styleable.awesomeedit_regex);
hint = ta.getString(R.styleable.awesomeedit_hint);
error = ta.getString(R.styleable.awesomeedit_error);
link = ta.getString(R.styleable.awesomeedit_link);
required = ta.getBoolean(R.styleable.awesomeedit_required, false);
email = ta.getBoolean(R.styleable.awesomeedit_email, false);
password = ta.getBoolean(R.styleable.awesomeedit_password, false);
nummber = ta.getBoolean(R.styleable.awesomeedit_number, false);
} finally {
ta.recycle();
}
LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = mInflater.inflate(R.layout.default_heading, this, true);
addView(view);
}
There are many ways how to do it.
For start read:
How to create custom components
How to create custom views in general
First, you should rather create a custom ViewGroup instead of a custom View.
I advice not to pass the List of actuall view instances (in your case Buttons) through your constructor, but better to just pass some information (title, what to do after click,...) and then build your buttons in the component itself..

Android #layout/list_item not resolved into its int value

I would like to create a custom linear layout (to work as some basic list) which accepts a custom parameter from xml, like this:
<MyLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
myns:layout_to_inflate="#layout/list_item"/>
Then, use it in the constructor:
String layoutToInflate = attrs.getAttributeValue(NAMESPACE, "layout_to_inflate");
I get "#layout/list_item". It is not resolved by the system into the int value which is accessible in R.layout.list_item.
Sure I can parse it and use Resources.getIdentifier to look up the ID, then inflate it, but I think that is not the way.
Then... what is the way? Can I get the system to resolve it directly into the int?
UPDATE:
list_item.xml:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Text here!" />
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:myns="http://com.example.layoutinflate"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.example.layoutinflate.MyLinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
myns:layout_to_inflate="#layout/list_item" />
</RelativeLayout>
Contents MyLinearLayout.java:
public class MyLinearLayout extends LinearLayout {
private static final String TAG = MyLinearLayout.class.getSimpleName();
public MyLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray styledAttributes = context.obtainStyledAttributes(attrs, R.styleable.MyLinearLayout);
int layoutId = styledAttributes.getResourceId(R.styleable.MyLinearLayout_layout_to_inflate, -1);
int layoutIdInt = styledAttributes.getInt(R.styleable.MyLinearLayout_layout_to_inflate, -1);
String str = styledAttributes.getString(R.styleable.MyLinearLayout_layout_to_inflate);
Log.d(TAG, Integer.toString(layoutId) + ";" + str + ";" + layoutIdInt); //-1; null; -1
styledAttributes.recycle();
}
}
Thanks!
Make sure you correctly define this custom attribute inside your attrs.xml. It has to be like this:
<attr name="layout_to_inflate" format="reference" />
<declare-styleable name="MyLinearLayout">
<attr name="layout_to_inflate" />
</declare-styleable>
Then you can get this value inside your MyLinearLayout object with this:
int layoutToInflateId = attributes.getResourceId(R.styleable.MyLinearLayout_layout_to_inflate, R.layout.<default_layout_to_inflate>);

how to define AttributeSet parameter for extended layout

I am having trouble understanding how to instantiate a custom layout in code. Specifically I do not know how to define the required AttributeSet parameter.
I have gone through the official docs but could not wrap my head around it :
http://developer.android.com/training/custom-views/create-view.html
http://developer.android.com/reference/android/util/AttributeSet.html
In my activity, i instantiate it with :
new MyCustomLayout(getActivity(), attrs)
But how do i define attrs?
MyCustomLayout.java
public class MyCustomLayout extends LinearLayout implements
OnClickListener {
...
...
public MyCustomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.mycustomxmllayout, this, true);
...
}
...
}
mycustomxmllayout.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<TextView
android:id="#+id/txt_1"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:lines="1"/>
<Button
android:id="#+id/btn_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="0dp"
android:minWidth="0dp"
android:background="#null"
android:text="x"
android:layout_weight="1"
/>
</merge>
Firstly, you should define your custom attributes in the res/values/attrs.xml, just add <declare-styleable>, for example:
<declare-styleable name="MyCustomLayout">
<attr name="showText" format="boolean" />
<attr name="buttonPosition" format="enum">
<enum name="left" value="0"/>
<enum name="right" value="1"/>
</attr>
</declare-styleable>
Now you can set your custom attrs via xml, like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto">
<your.package.name.MyCustomLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
custom:showText="true"
custom:buttonPosition="left" />
</LinearLayout>
And in your constructor you can grab that attrs:
public MyCustomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.MyCustomLayout,
0, 0);
try {
mShowText = a.getBoolean(R.styleable.MyCustomLayout_showText, false);
mButtonPos = a.getInteger(R.styleable.MyCustomLayout_buttonPosition, 0);
} finally {
a.recycle();
}
}
If you want to set your attrs programmatically, you should create public getters/setters for that:
private boolean mShowText;
private Integer mButtonPos;
public void setButtonPos(int pos) {
mButtonPos = pos;
}
public void setShowText(boolean showText) {
mShowText = showText;
}
After that you can set your attrs programmatically:
MyCustomLayout layout = new MyCustomLayout(getActivity());
layout.setButtonPos(0);
layout.setShowText(true);

Reference a string value in a styleable attribute

I am trying to extend an android.widget.Button and added a styleable attribute to my custom widget, which should hold a reference to a value in res/values/strings.xml.
<resources>
<attr name="infoText" format="reference" />
<declare-styleable name="FooButton">
<attr name="infoText" />
</declare-styleable>
</resources
In my layout I have something like this:
<LinearLayout
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal">
<com.example.FooButton
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="#+id/fooButton"
infoText="#string/fooButtonInfoText" />
</LinearText>
My res/values/strings.xml looks like this:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="fooButtonInfoText">BAR</string>
</resources>
The extraction of the attribute value in my custom FooButton looks like this:
TypedArray typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.FooButton);
Integer infoTextId = typedArray.getResourceId(R.styleable.FooButton_infoText, 0);
if (infoTextId > 0) {
infoText = context.getResources().getString(infoTextId);
}
typedArray.recycle();
I've got these three constructors implemented:
public FooButton(Context context) {
super(context);
}
public FooButton(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
setInfoText(context, attributeSet);
}
public FooButton(Context context, AttributeSet attributeSet, int defStyle) {
super(context, attributeSet, defStyle);
setInfoText(context, attributeSet);
}
The method FooButton.setInfoText(context, attributeSet) is called every time there is a FooButton declared.
I am fighting this problem for too long and read dozens of Stackoverflow questions... why does this not work?
You must declare the namespace for custom attributes. It should look like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/auto"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:orientation="horizontal">
<com.example.FooButton
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="#+id/fooButton"
app:infoText="#string/fooButtonInfoText" />
</LinearText>

Categories

Resources