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
...
Related
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);
}
I am trying to use two properties from attrs.xml such as: content and handle. When I use them while building the layout the view goes away. I have been trying the bug for 3 days and nothing worked.
Help is much appreciated!
Code in Context
<hh.hhh.app.android.hhhh.widget.ExpandablePanel
android:id="#+id/expandablePanel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:handle="#+id/expandButton"
app:content="#+id/listViewProduct"
app:collapsedHeight="50dip"
app:animationDuration="25">
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/listViewProduct"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/expandButton"/>
</hh.hhh.app.android.hhhh.widget.ExpandablePanel>
attrs.xml code
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ExpandablePanel">
<attr name="handle" format="reference" />
<attr name="content" format="reference" />
<attr name="collapsedHeight" format="dimension"/>
<attr name="animationDuration" format="integer"/>
</declare-styleable>
</resources>
I am sharing my own custom class as an example. So for all custom function you need to first check if it is in EditMode or not. isInEditMode() tells us is the layout is being render by IDE preview or by phone. IDE preview is not so much powerful to run our custom codes, so it is recommended to do not run our custom while rendering by preview. I am attaching my custom class to just understand the exact scenario.
public class DynamicImageView extends ImageView {
static DynamicImageTypeListener masterImageTypeListener;
DynamicImageTypeListener imageTypeListener;
int imageResourceArray;
public DynamicImageView(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
initValues(attr, defStyle);
}
public DynamicImageView(Context context, AttributeSet attr) {
super(context, attr);
initValues(attr, 0);
}
public DynamicImageView(Context context) {
super(context);
}
public static void setMasterImageTypeListener(DynamicImageTypeListener masterImageTypeListener) {
DynamicImageView.masterImageTypeListener = masterImageTypeListener;
}
public void setImageTypeListener(DynamicImageTypeListener imageTypeListener) {
this.imageTypeListener = imageTypeListener;
}
private void initValues(AttributeSet attr, int defStyle) {
if (!isInEditMode()) { //This code will run only on phone not by preview.
TypedArray a = getContext().obtainStyledAttributes(attr, R.styleable.DynamicImageView, defStyle, 0);
imageResourceArray = a.getResourceId(0, 0);
updateImage();
a.recycle();
}
}
public void updateImage() {
TypedArray imgs = getResources().obtainTypedArray(imageResourceArray);
int imageIndex = imageTypeListener != null ? imageTypeListener.getImageType(getContext()) : (masterImageTypeListener != null ? masterImageTypeListener.getImageType(getContext()) : 0);
setImageResource(imgs.getResourceId(imageIndex, -1));
}
}
Hope it will help :)
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);
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>
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);