declare-styleable drawableTop - android

I try implement compound view for my dashboard activity. It's Button with TextView. Need for my theme.
I have attr.xml file
<declare-styleable name="DashboardButton">
<attr name="drawableTop" format="reference"/> <!-- i want set android:drawableTop of button-->
<attr name="btnText" format="string" /> <!-- text about button functionality-->
<attr name="tvText" format="string" /> <!-- additional information -->
</declare-styleable>
also have dashboard_button.xml layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<Button
android:id="#+id/btn"
style="#style/DashboardButton"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true" />
<TextView
style="#style/btn_add_info"
android:id="#+id/txtView"
android:text="0"/>
</RelativeLayout>
also have custom component DashboardButton
public class DashboardButton extends RelativeLayout {
private TextView txtView;
private Button btn;
public DashboardButton(Context context) {
super(context);
}
public DashboardButton(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.dashboard_button, this);
loadViews(attrs);
}
public DashboardButton(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.dashboard_button, this);
loadViews(attrs);
}
private void loadViews(AttributeSet attrs) {
txtView = (TextView) findViewById(R.id.txtView);
btn = (Button) findViewById(R.id.btn);
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.DashboardButton);
txtView.setText(a.getString(R.styleable.DashboardButton_tvText));
btn.setText(a.getString(R.styleable.DashboardButton_btnText));
//i think issue here but can't understand what i should do
Drawable topDrawable = a.getDrawable(R.styleable.DashboardButton_drawableTop);
btn.setCompoundDrawables(null, topDrawable, null, null);
a.recycle();
}
public void setTxtViewText(CharSequence text) {
txtView.setText(text);
}
}
and finally i have
<my.package.name.DashboardButton
android:id="#+id/Btn"
style="#style/DashboardButton"
app:btnText="#string/my_function"
app:tvText="#string/add_info"
app:drawableTop="#drawable/function_logo">
</my.package.name.DashboardButton>
All work fine, except app:drawableTop drawable not load at run time, I dont see drawable. Can you help me by advice?

You need to set the bounds for your drawable:
topDrawable.setBounds( new Rect( 0, 0, w, h ) );
or alternatively use:
btn.setCompoundDrawablesWithIntrinsicBounds(null, topDrawable, null, null);

Related

Custom view default style not wokring without style tag

I have researched a lot and find many sources to this problem but no solutions working for me I don't know what's going wrong.
Some resources i have look into
Custom view style, android's attributes are ignored
https://androidpedia.net/en/tutorial/1446/creating-custom-views
https://infinum.com/the-capsized-eight/how-to-support-themes-in-custom-views-for-android-apps
This is repo for this problem
https://github.com/burhankhanzada199888/CustomView-Style-StackOverflow-Question/tree/master/app
I have 2 custom view in my activity first without style tag and second with style tag but cardView style only apply when using in xml
custom_view.xml
<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"
tools:parentTag="com.google.android.material.card.MaterialCardView"
tools:style="#style/CustomCardView">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="4dp"
android:background="?colorSurface"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="#+id/imageView"
android:layout_width="100dp"
android:layout_height="100dp"
android:padding="8dp"
android:src="#mipmap/ic_launcher"
app:srcCompat="#mipmap/ic_launcher" />
<TextView
android:id="#+id/textView"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?colorAccent"
android:gravity="center"
android:text="Lorem ipsum"
android:textColor="#android:color/white"
android:textSize="25sp" />
</LinearLayout>
</merge>
activity_main.xml
<com.myapp.CustomCardView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.myapp.CustomCardView
style="#style/CustomCardView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
CustomCardView.java
public class CustomCardView extends MaterialCardView {
public CustomCardView(Context context) {
this(context, null);
}
public CustomCardView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomCardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomCardView, defStyleAttr, R.style.CustomCardView);
float imageSize = a.getDimension(R.styleable.CustomCardView_imageSize, 0);
float textSize = a.getDimension(R.styleable.CustomCardView_textSize, 0);
a.recycle();
inflate(context, R.layout.custom_view, this);
ImageView imageView = findViewById(R.id.imageView);
imageView.getLayoutParams().height = (int) imageSize;
imageView.getLayoutParams().width = (int) imageSize;
TextView textView = findViewById(R.id.textView);
textView.setTextSize(textSize);
}
}
styles.xml
<style name="CustomCardView" parent="Widget.MaterialComponents.CardView">
<item name="cardElevation">8dp</item>
<item name="cardCornerRadius">16dp</item>
<item name="android:layout_margin">8dp</item>
<item name="cardBackgroundColor">?colorPrimary</item>
<item name="imageSize">150dp</item>
<item name="textSize">50sp</item>
</style>
attrs.xml
<declare-styleable name="CustomCardView">
<attr name="imageSize" format="dimension|reference" />
<attr name="textSize" format="dimension|reference" />
</declare-styleable>
You can add in the attrs.xml:
<attr format="reference" name="customCardViewStyle"/>
Change the constructor:
public CustomCardView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
to
public CustomCardView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.customCardViewStyle);
}
Then in your app theme add:
<item name="customCardViewStyle">#style/CustomCardView</item>
Just pay attention to <item name="android:layout_margin">...</item>. The LayoutParams are specific to the parent view type and you can't apply it with a theme.You can set the layout attributes in a style on in the layout.
It happens because you've removed super calls in public CustomCardView(Context context) and public CustomCardView(Context context, AttributeSet attrs) constructors. Indeed your constructors must be like these:
public CustomCardView(Context context) {
super(context)
this(context, null);
}
and
public CustomCardView(Context context, AttributeSet attrs) {
super(context, attrs)
this(context, attrs, 0);
}

Custom View with two TextViews

I want to create a custom view with 2 TextViews, with possibility change text and text appearances from xml. This view should have two state - normal and selected (TextViews style should be different for each state).
I need some example for it.
Custom views are pretty basic and there are examples all over the internet. For something simple like two text views, it's usually easiest to extend LinearLayout.
Here is the LinearLayout with two text views, arranged side by side.
res/double_text.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/left_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"/>
<TextView
android:id="#+id/right_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"/>
</LinearLayout>
Next we define a styleable resource block so we can add custom attributes to our custom layout.
res/values/attrs.xml
<resources>
<declare-styleable name="DoubleText">
<attr name="leftText" format="string" />
<attr name="rightText" format="string" />
<attr name="android:ems" />
</declare-styleable>
</resources>
Next the class file for DoubleText custom view. In here we pull out the custom attributes and set each of the TextViews.
DoubleTextView.java
public class DoubleTextView extends LinearLayout {
LinearLayout layout = null;
TextView leftTextView = null;
TextView rightTextView = null;
Context mContext = null;
public DoubleTextView(Context context) {
super(context);
mContext = context;
}
public DoubleTextView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.DoubleText);
String leftText = a.getString(R.styleable.DoubleText_leftText);
String rightText = a.getString(R.styleable.DoubleText_rightText);
leftText = leftText == null ? "" : leftText;
rightText = rightText == null ? "" : rightText;
String service = Context.LAYOUT_INFLATER_SERVICE;
LayoutInflater li = (LayoutInflater) getContext().getSystemService(service);
layout = (LinearLayout) li.inflate(R.layout.double_text, this, true);
leftTextView = (TextView) layout.findViewById(R.id.left_text);
rightTextView = (TextView) layout.findViewById(R.id.right_text);
leftTextView.setText(leftText);
rightTextView.setText(rightText);
a.recycle();
}
public DoubleTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
}
#SuppressWarnings("unused")
public void setLeftText(String text) {
leftTextView.setText(text);
}
#SuppressWarnings("unused")
public void setRightText(String text) {
rightTextView.setText(text);
}
#SuppressWarnings("unused")
public String getLeftText() {
return leftTextView.getText().toString();
}
#SuppressWarnings("unused")
public String getRightText() {
return rightTextView.getText().toString();
}
}
Finally, using the custom class is as simple as declaring it in a layout file.
<RelativeLayout
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="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:showIn="#layout/activity_main"
tools:context=".MainActivity">
<TextView
android:id="#+id/main_text"
android:text="Hello World!"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/custom"/>
<example.com.test.DoubleTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:leftText="Text Left"
app:rightText="Text Right"
android:layout_below="#+id/main_text"/>
</RelativeLayout>
Easy peasy.

NumberFormatException when using styleable attrs

I created a custom view for Android which renders two inner views to store a key and a value in two columns. The class looks like this:
public class KeyValueRow extends RelativeLayout {
protected TextView mLabelTextView;
protected TextView mValueTextView;
public KeyValueRow(final Context context) {
super(context);
init(context, null, 0);
}
public KeyValueRow(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public KeyValueRow(final Context context,
final AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
protected void init(final Context context,
final AttributeSet attrs, int defStyle) {
View layout = ((LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.key_value_row, this, true); // line 46
mLabelTextView = (TextView) layout.findViewById(
R.id.key_value_row_label);
mValueTextView = (TextView) layout.findViewById(
R.id.key_value_row_value);
}
public void setLabelText(final String text) {
mLabelTextView.setText(text);
}
public void setValueText(final String text) {
mValueTextView.setText(text);
}
}
The associated layout file layout/key_value_row.xml looks like this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="1.0">
<TextView
android:id="#+id/key_value_row_label"
style="#style/KeyValueLabel"
android:layout_weight=".55"
tools:text="Label"/>
<TextView
android:id="#+id/key_value_row_value"
style="#style/KeyValueValue"
android:layout_weight=".45"
tools:text="Value"/>
</LinearLayout>
This can be used as follows in a layout:
<com.example.myapp.customview.KeyValueRow
android:id="#+id/foobar"
style="#style/KeyValueRow" />
Until here everything works fine!
The challenge
Now, I want to allow custom settings for the layout_weight attributes of both inner TextViews. Therefore, I prepared the attributes in values/attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="KeyValueRow">
<attr name="label_layout_weight" format="float" />
<attr name="value_layout_weight" format="float" />
</declare-styleable>
</resources>
First question would be: is float the correct format for layout_weight?
Next, I would apply them in the layout file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="1.0">
<TextView
android:id="#+id/key_value_row_label"
style="#style/KeyValueLabel"
android:layout_weight="?label_layout_weight"
tools:text="Label"/>
<TextView
android:id="#+id/key_value_row_value"
style="#style/KeyValueValue"
android:layout_weight="?value_layout_weight"
tools:text="Value"/>
</LinearLayout>
Then they can be used in the example:
<com.example.myapp.customview.KeyValueRow
android:id="#+id/foobar"
style="#style/KeyValueRow"
custom:label_layout_weight=".25"
custom:value_layout_weight=".75" />
When I run this implementation the following exception is thrown:
Caused by: java.lang.NumberFormatException: Invalid float: "?2130772062"
at java.lang.StringToReal.invalidReal(StringToReal.java:63)
at java.lang.StringToReal.parseFloat(StringToReal.java:310)
at java.lang.Float.parseFloat(Float.java:300)
at android.content.res.TypedArray.getFloat(TypedArray.java:288)
at android.widget.LinearLayout$LayoutParams.<init>(LinearLayout.java:1835)
at android.widget.LinearLayout.generateLayoutParams(LinearLayout.java:1743)
at android.widget.LinearLayout.generateLayoutParams(LinearLayout.java:58)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:757)
at android.view.LayoutInflater.inflate(LayoutInflater.java:492)
at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
at com.example.myapp.customview.KeyValueRow.init(KeyValueRow.java:46)
at com.example.myapp.customview.KeyValueRow.<init>(KeyValueRow.java:28)
... 33 more
You can use
private void applyAttributes(Context context, AttributeSet attrs) {
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs, R.styleable.KeyValueRow, 0, 0);
try {
labelWeight = a.getFloat(
R.styleable.KeyValueRow_label_layout_weight, 0.55f);
valueWeight = a.getFloat(
R.styleable.KeyValueRow_value_layout_weight, 0.45f);
} finally {
a.recycle();
}
}
and after this you can apply this via:
mLabelTextView = (TextView) layout.findViewById(R.id.key_value_row_label);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, labelWeight);
mLabelTextView.setLayoutParams(layoutParams);
To get it running replace android:layout_weight="?label_layout_weight" with some default value such as android:layout_weight="0.5" in layout/key_value_row.xml. It will be overwritten.

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);

Get animation from custom attribute in constructor

I have a custom view, extending LinearLayout and im trying to add some custom attributes, like this:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- other stuff -->
<declare-styleable name="UIPlayer">
<attr name="team" format="integer" />
<attr name="playAnimation" format="reference" />
</declare-styleable>
</resources>
My custom view is like this:
public class UIPlayer extends LinearLayout
{
// . . .
public UIPlayer(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
initAttrs(context, attrs);
}
public UIPlayer(Context context, AttributeSet attrs)
{
super(context, attrs);
initAttrs(context, attrs);
}
public void initAttrs(Context context, AttributeSet attrs)
{
TypedArray taPlayer = context.obtainStyledAttributes(attrs, R.styleable.UIPlayer);
int team = taPlayer.getInt(R.styleable.UIPlayer_team, -1);
player.setTeam(team);
int animPlayId = taPlayer.getResourceId(R.styleable.UIPlayer_playAnimation, -1);
try
{
animPlay = AnimationUtils.loadAnimation(context, animPlayId);
}
catch (NotFoundException e)
{
d("anim", "onPlay not found! " + animPlayId);
}
taPlayer.recycle();
}
// . . .
}
My layout file is like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:devn="http://schemas.android.com/apk/res/devN.games.mygame"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clipChildren="false"
android:orientation="vertical"
android:weightSum="3.0" >
<devN.games.UIPlayer
android:id="#+id/player"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:clipChildren="false"
android:gravity="top"
devn:playAnimation="#anim/play"
devn:team="2" >
</devN.games.UIPlayer>
<!-- ... -->
</LinearLayout>
What im doing wrong and i cant retrive the animation from within the contstructor? (the "strange fact" is that the team attribute is working fine)
After a lot of studies in android source code, i tried the overload methode:
obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
and it worked like a charm!! :)
So the correct code is:
TypedArray taPlayer = context.obtainStyledAttributes(attrs, R.styleable.UIPlayer, 0, 0);

Categories

Resources