Custom compound view and .findViewById() - android

I am really worried about the .findViewById() method and its use in a custom compound view creation. I am not sure about the exact place that it is guaranteed that it will never return null.
Let's say I have this custom compound view and that it is added to some .xml like this:
<com.app.path.TwoButtonsView
android:id="#+id/ok_cancel_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
two_buttons_view.xml:
<?xml version="1.0" encoding="utf-8"?>
<merge>
<LinearLayout 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:orientation="horizontal">
<TextView
android:id="#+id/first_button"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:clickable="true"
android:gravity="center"
android:textAllCaps="true"/>
<TextView
android:id="#+id/second_button"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:clickable="true"
android:gravity="center"
android:textAllCaps="true"/>
</LinearLayout>
</merge>
TwoButtonsView.java
public class TwoButtonsView extends LinearLayout {
// views
private TextView mFirstButtonView;
private TextView mSecondButtonView;
public TwoButtonsView(Context context) {
super(context);
init(context, null);
}
public TwoButtonsView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public TwoButtonsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public TwoButtonsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.two_buttons_view, this);
// retrieve views
mFirstButtonView = (TextView) findViewById(R.id.first_button); // is there a chance it will return null?
mSecondButtonView = (TextView) findViewById(R.id.second_button); // is there a chance it will return null?
}
}
My question is: is there any chance that .findViewById(RESOURCE_ID) will return null right after calling inflater.inflate(R.layout.two_buttons_view, this);, or should I call my init() method on onFinishInflate() callback?

My question is: is there any chance that .findViewById(RESOURCE_ID)
will return null right after calling
inflater.inflate(R.layout.two_buttons_view, this)
No, there is not, as long as the views you are looking for are in the layout and you explicitly added to the view's hierarchy. In the end, findViewById loops on View[], filled up by addView.
A small note about
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.two_buttons_view, this);
ViewGroup has the static method inflate, so you don't need to retrieve the inflater calling getSystemService

Related

Custom LinearLayout with an Icon

I got a bunch of form controls that all show an Icon to the left, like so:
For this I have this code:
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="#dimen/icon_edittext_height"
android:layout_gravity="top"
android:layout_marginRight="#dimen/icon_edittext_marginRight"
android:layout_marginTop="#dimen/icon_edittext_margintop"
android:src="#drawable/ic_location_city_black_24dp"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.TextInputLayout
android:id="#+id/create.data.city.layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<AutoCompleteTextView
android:id="#+id/create.data.city"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/create_event_city"
android:imeOptions="actionNext"
android:inputType="textCapSentences"
android:maxLines="1"
/>
</android.support.design.widget.TextInputLayout>
</FrameLayout>
</LinearLayout>
Now because I have around 20 controls, I dont want to repeat the:
<LinearLayout>
<ImageView/>
<FrameLayout>
...content....
</FrameLayout>
</LinearLayout>
schema 20 times, but I would like to do something like this:
<MycustomLayout>
<EditText .../>
</MycustomLayout>
<MycustomLayout>
<Spinner ... />
</MycustomLayout>
So basically I want to create a custom Layout that extends Linear, and I want to be able to use it in the same way that I use LinearLayout, while having an icon prepended at all times.
You can try using include tag in your layout to include the part which is repeating. Or if that doesn't work you can try something like this:
public class CustomControl extends LinearLayout {
public CustomControl (Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.custom_layout, this);
}
}
Where custom_layout is the layout for you custom view.
Then you can just do in your xml:
<CustomControl />
UPD:
In order to add child views in xml just override onFinishInflate and add views like this:
protected void onFinishInflate() {
View[] children = detachChildren();
for (int i = 0; i < children.length; i++)
this.addView(children[i]);
}
No need to add too much layouts. Just try to use this...
<android.support.design.widget.TextInputLayout
android:id="#+id/create.data.city.layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<AutoCompleteTextView
android:id="#+id/create.data.city"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="City"
android:drawableLeft="#drawable/ic_location_city_black_24dp"
android:drawablePadding="5dp"
android:imeOptions="actionNext"
android:inputType="textCapSentences"
android:maxLines="1" />
</android.support.design.widget.TextInputLayout>
Solved it myself like so:
public class IconFieldLinearLayout extends LinearLayout {
private Drawable mDefaultDrawable;
public IconFieldLinearLayout(Context context) {
super(context);
init(context, null);
}
public IconFieldLinearLayout(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public IconFieldLinearLayout(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public IconFieldLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
this.setOrientation(HORIZONTAL);
mDefaultDrawable = getResources().getDrawable(R.drawable.tv_avatar_default);
initAttr(context, attrs);
}
private void initAttr(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.IconFieldLinearLayout, 0, 0);
Drawable icon = a.getDrawable(R.styleable.IconFieldLinearLayout_icon);
a.recycle();
initIcon(context, icon);
}
else {
initIcon(context, mDefaultDrawable);
}
}
private void initIcon(Context context, Drawable icon) {
ImageView imageView = new ImageView(context);
imageView.setColorFilter(ContextCompat.getColor(context, R.color.colorAccent));
int dimension = getResources().getDimensionPixelSize(R.dimen.icon_edittext_height);
int marginRight = getResources().getDimensionPixelSize(R.dimen.icon_edittext_marginRight);
int marginTop = getResources().getDimensionPixelSize(R.dimen.icon_edittext_margintop);
LinearLayout.LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, dimension);
layoutParams.gravity = Gravity.TOP;
layoutParams.setMargins(0, marginTop, marginRight, 0);
imageView.setImageDrawable(icon);
addView(imageView, layoutParams);
}
}
Did not really need to put the FrameLayout in there.

cannot dynamically add views android

I'm trying to implement a custom view with textview children added dynamically. But it isn't showing any result. there are no error, just nothing shows up:
My Class:
public class CustomPasscodeEntryView extends LinearLayout {
private Context mContext;
private CustomPasscodeEntryView thisView;
public CustomPasscodeEntryView(Context context) {
super(context);
/* same as below */
}
public CustomPasscodeEntryView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
/*same as below*/
}
public CustomPasscodeEntryView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
thisView = (CustomPasscodeEntryView) inflater.inflate(R.layout.view_passcode_entry,this);
}
public void addDigit(int digit){
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final pinView pin = (pinView) inflater.inflate(R.layout.password_bullet_view,null);
pin.setDigit(digit);
thisView.addView(pin, thisView.getChildCount());
pin.setVisibility(VISIBLE);
}
public void deleteDigit(){
if(getChildCount() > 0)
thisView.removeView(thisView.getChildAt(getChildCount()-1));
}
}
class pinView extends TextView {
int digit;
public pinView(Context context) {
super(context);
}
public pinView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public pinView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setText('•');
}
public void setDigit(int digit) {
this.digit = digit;
this.setText(Integer.toString(digit));
}
}
and my XMLs:
view_passcode_entry:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:animateLayoutChanges="true">
</LinearLayout>
password_bullet_view:
<com.github.lockpin.lollipin.lib.views.pinView
xmlns:android="http://schemas.android.com/apk/res/android"
android:textColor="#android:color/white"
android:text="•"
android:textSize="20sp"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="2.5dp"
/>
EDIT: the view in my activity:
<com.github.lockpin.lollipin.lib.views.CustomPasscodeEntryView
android:id="#+id/custom_passcode_entry_view"
android:layout_width="300dp"
android:layout_below="#id/pin_code_text_view"
android:layout_centerHorizontal="true"
android:layout_height="40dp"/>
I cant see what's wrong, and I've followed tutorials online and made a few changes. Am I missing something here? Or is this plainly wrong?
You use wrong view id view_passcode_entry when inflating view, which is instance of LinearLayout, and typecast it to CustomPasscodeEntryView.
You only need to use this when call addView() in addDigit() because CustomPasscodeEntryView is also instance of LinearLayout. So layout view_passcode_entry can be removed because it is not needed anymore.
this.addView(pin, this.getChildCount());
But if you want to add view view_passcode_entry as child of CustomPasscodeEntryView instance then you need to add thisView also
private ViewGroup thisView;
...
thisView = (ViewGroup) inflater.inflate(R.layout.view_passcode_entry, this);
this.addView(thisView, 0);
and then if you want to use view_passcode_entry as parent of pinView then you can use
thisView.addView(pin, thisView.getChildCount());
Is it because of you have already mentioned the height of CustomPasscodeEntryView in the xml (android:layout_height="40dp")?
Can you try wrap_content instead?

FindViewById returns null on custom relative layout

In init method, findViewById returns null on R.id.disable_view_content.
public class DisableView extends RelativeLayout {
private View content;
private View disableView;
private int disableBackgroundColor;
private boolean isEnabled;
public DisableView(Context context) {
super(context);
init();
}
public DisableView(Context context, AttributeSet attrs) {
super(context, attrs);
obtainAttributes(context, attrs);
init();
}
public DisableView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
obtainAttributes(context, attrs);
init();
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public DisableView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
obtainAttributes(context, attrs);
init();
}
private void obtainAttributes(Context context, AttributeSet attributeSet) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attributeSet, R.styleable.DisableView, 0, 0);
disableBackgroundColor = typedArray.getColor(R.styleable.DisableView_disableBackground, 0x00000000);
isEnabled = typedArray.getBoolean(R.styleable.DisableView_enabled, false);
typedArray.recycle();
}
private void init() {
content = findViewById(R.id.disable_view_content);
disableView = new View(getContext());
RelativeLayout.LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
lp.addRule(RelativeLayout.ALIGN_TOP, R.id.disable_view_content);
lp.addRule(RelativeLayout.ALIGN_BOTTOM, R.id.disable_view_content);
lp.addRule(RelativeLayout.ALIGN_LEFT, R.id.disable_view_content);
lp.addRule(RelativeLayout.ALIGN_TOP, R.id.disable_view_content);
disableView.setBackgroundResource(disableBackgroundColor);
disableView.setLayoutParams(lp);
setEnabled(isEnabled);
}
public void setEnabled(boolean enabled) {
content.setEnabled(enabled);
if(enabled) disableView.setVisibility(View.GONE);
else disableView.setVisibility(View.VISIBLE);
}
}
Here's xml
<com....view.common.DisableView
android:id="#+id/..._disableView_...Disable"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="#id/disable_view_content"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/..._textView_..."
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/shape_rect_green"
android:gravity="center"
android:paddingTop="18dp"
android:paddingBottom="18dp"
android:layout_marginBottom="12dp"
android:textSize="#dimen/size_text"
android:text="#string/..."
android:textColor="#android:color/white"
android:duplicateParentState="true"/>
</LinearLayout>
</com....view.common.DisableView>
id disable_view_content is in attr.xml
<resources>
<item type="id" name="disable_view_content"/>
</resources>
Everything seems fine, view with the id disable_view_content is a direct child of DisableView. But either in edit mode or on app, i get a null pointer because the reference content is null.
Edit: Here's the stack trace:
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.setEnabled(boolean)' on a null object reference
at com....view.common.DisableView.setEnabled(DisableView.java:71)
at com....view.common.DisableView.init(DisableView.java:67)
at com....view.common.DisableView.<init>(DisableView.java:35)
You are calling findViewById() far too early in the View's lifecycle.
Your init() is called directly from your constructor, and before you have even created a layout for your own View. At this point in time, your View has no layout and has no children either.
The earliest you can access child Views is after one of the addView() methods has been called.

Adding compound view class into view programatically in android

Hi I am trying to add compound view class into my view programmatically. But I don't know how to do that.
I can add directly it in to layout and thats working fine.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mainLlt"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.example.androidcustomvliveapplication.widget.CustomCardSection
android:id="#+id/cardSection2"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</com.example.androidcustomvliveapplication.widget.CustomCardSection>
</LinearLayout>
Above thing working fine. But I want to add above composite layout programmatically.
LIke I want to do like this
mainLlt.add(customcardsection)
Need Some help. Thank you.
public class CustomCardSection extends RelativeLayout
{
int section;
#SuppressLint("NewApi")
public CustomCardSection(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes)
{
super(context, attrs, defStyleAttr, defStyleRes);
}
public CustomCardSection(Context context, AttributeSet attrs,
int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
public CustomCardSection(Context context, AttributeSet attrs)
{
super(context, attrs);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.custom_card_section, this, true);
}
public CustomCardSection(Context context)
{
super(context);
}
}

Custom view not inflating layout when added programmatically

I'm trying to add a custom view to a linear layout programmatically. I want the custom view to inflate a layout I've already created. When I create an instance of the custom view and add it, a spot shows up where the view is, but the layout didn't seem to be inflated (set the background color of my custom layout to green, but I don't see green in the space, nor the image in the layout).
My custom view:
public class AddressView extends View {
public AddressView(Context context) {
super(context);
View.inflate(context, R.layout.place_address, null);
}
public AddressView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
View.inflate(context, R.layout.place_address, null);
}
public AddressView(Context context, AttributeSet attrs) {
super(context, attrs);
View.inflate(context, R.layout.place_address, null);
}
}
My custom view layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/address_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#00ff00" >
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/house_1" />
</RelativeLayout>
My instantiation in an activity:
LinearLayout content = (LinearLayout) findViewById(R.id.content);
AddressView addressView = new AddressView(this);
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 400);
content.addView(addressView, 0, lp);
My R.id.content (The linear layout is the only child of a scrollview):
<LinearLayout
android:id="#+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<FrameLayout
android:id="#+id/thumbnail_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ImageView
android:id="#+id/thumbnail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitXY"
android:src="#drawable/test_house" />
</FrameLayout>
</LinearLayout>
I eventually worked around this by extending FrameLayout instead of View, and passing this to the inflate call. I think the view was being added and inflating, but it didn't know how to lay out the children correctly, which is resolved if you pass the parent to the inflate call initially:
public class AddressView extends FrameLayout {
public AddressView(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.place_address, this);
}
public AddressView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
LayoutInflater.from(context).inflate(R.layout.place_address, this);
}
public AddressView(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.place_address, this);
}
}

Categories

Resources