I have a Custom View "CustomLayout" subclass of RelativeLayout.
public class CustomLayout extends RelativeLayout implements View.OnClickListener{
private String titleText;
private Context context;
private AttributeSet attrs;
private ImageView iv1,iv2;
private TextView title,tv2;
private TextView textView;
private Button button;
public CustomLayout(Context context) {
this(context, null);
}
public CustomLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
this.attrs = attrs;
init();
}
public CustomLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.context = context;
this.attrs = attrs;
initAttributes(context,attrs,defStyle);
}
private void initAttributes(Context context, AttributeSet attrs, int defStyleAttr) {
// declare styleable here to programatically set custom view style attributes
final int[] styleable = new int[] {
android.R.attr.src,
android.R.attr.textAppearance,
android.R.attr.text,
android.R.attr.textSize
};
Arrays.sort(styleable);
TypedArray a = context.obtainStyledAttributes(attrs, styleable, defStyleAttr,0);
...
}
I don't know how to set the properties pro-grammatically by passing arguments in the last constructor ,which takes three arguments, from my Activity. I know to do it from layout.xml file as shown in code below.Please help
<com.example.customview.CustomLayout
android:id="#+id/view1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:selectableItemBackground"
app:titleText="HappyTrips Editors"
app:descriptText="#string/content"
app:titleTextColor="#FF0000"
app:descriptTextColor="#0000FF"
app:titleTextSize="8sp"
app:descriptTextSize="6sp"
app:bgColor="#FFFF00"/>
In my ListAdapter
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
View result=convertView;
if (convertView==null){
result=inflater.inflate(R.layout.layout_list_items,null);
CustomLayout object = (CustomLayout)result.findViewById(R.id.view1);
How to pass arguments containing AttributeSet in CustomLayout constructor from here
in layout.xml you can user style,it will work or
CustomLayout object=new CustomLayout(new ContextThemeWrapper(this,R.style,customStyle));
Related
I have a xml layout with an element i use multiple times... so i decided to use <include> in the xml to avoid excess code.
My problem is that i want to make a class that is connected to this included xml component and reference to it, rather than writing the same code multiple times.
I tried to read up on a custom view component and created a class ParcelPopbarView:
public ParcelTopBarView extends View {
/...
public ParcelTopBarView(Context context, ParcelListItem parcelListItem) {
super(context);
this.parcelListItem = parcelListItem;
this.context = context;
titleTextView = findViewById(R.id.title_textview);
subtitleTextView = findViewById(R.id.subtitle_textview);
deliveryInfoTextView = findViewById(R.id.delivery_info_textview);
thumbnailLogo = findViewById(R.id.thumbnail_logo);
}
public void setTopbar(){
titleTextView.setText("hello!");
}
But i felt like custom views mostly is about drawing on a canvas, i am not doing that... so i dont know if using view is right either.
Either way, the titleTextView is null cause it cannot find the refrence to the xml file, and i have no idea how to reference to it hahah.
Does anyone have a smart solution to how i can do this the right way?
You need to inflate the layout for all the constructors of your custom view to be able to access them later.
public class ParcelTopBarView extends View {
public ParcelTopBarView(Context context) {
super(context);
init();
}
public ParcelTopBarView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public ParcelTopBarView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public ParcelTopBarView(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
View view = inflate(getContext(), R.layout.parcel_top_bar, this);
titleTextView = findViewById(R.id.title_textview);
subtitleTextView = findViewById(R.id.subtitle_textview);
deliveryInfoTextView = findViewById(R.id.delivery_info_textview);
thumbnailLogo = findViewById(R.id.thumbnail_logo);
}
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?
Where can i add the listeners so the are always added, no matters if the custom view is created from xml or if is inflated.
I try on onFinishInflate but is only called if the view is created from xml layout. There is no OnViewCreated or something like that where i can be sure that all inside views are created and an listener can be attached.
Update: Typical compound view
public class CustomView extends RelativeLayout{
//called directy
public CustomView(Context context) {
super(context);
initializeViews(context);
}
private void initializeViews(final Context context) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.myview_layout, this);
}
//called when used inside xml layout
public ChatView(Context context, AttributeSet attrs) {
super(context, attrs);
initializeViews(context);
}
//idem
public ChatView(Context context,
AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
initializeViews(context);
}
I want to create a custom View, which shows a Card with follwing Contents:
TextView (Caption)
TextView (Description)
LinearLayout (innerLayout)
So i just extended a LinearLayout and inflated my Layout file with it:
public class FrageContainerView extends LinearLayout {
private TextView objTextViewCaption;
private TextView objTextViewDescription;
private String caption;
private String description;
private LinearLayout objLayoutInner;
public FrageContainerView(Context context) {
this(context, null);
}
public FrageContainerView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs);
}
public FrageContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context, attrs);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public FrageContainerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize(context, attrs);
}
private void initialize(Context context, AttributeSet attrs) {
TypedArray a =
context.obtainStyledAttributes(attrs, R.styleable.options_frageContainerView, 0, 0);
caption = a.getString(R.styleable.options_frageContainerView_caption);
description = a.getString(R.styleable.options_frageContainerView_description);
a.recycle();
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
LayoutInflater inflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.view_fragecontainer, this, true);
objLayoutInner = (LinearLayout) findViewById(R.id.linearlayout_inner);
objTextViewCaption = (TextView) findViewById(R.id.textview_caption);
objTextViewDescription = (TextView) findViewById(R.id.textview_description);
objTextViewCaption.setText(caption);
objTextViewDescription.setText(description);
}
A user which uses my custom View should be able to add his own Components preferably inside the XML like this:
<FrageContainerView
android:layout_width="match_parent"
android:layout_height="match_parent"
custom:caption="Hallo"
custom:description="LOLOLOL"
android:background="#FF00FF00">
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="sdsdfsdf"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="sdsdfsdf"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="sdsdfsdf"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="sdsdfsdf"/>
</FrageContainerView>
The current State is, that the defined EditText's are inflated inside my custom View. I want the EditTexts in my Example to be added to the InnerLayout instead to append them to Custom View.
What's the best approach to do this?
The essence of this problem is how to add child Views to a GroupView that is itself a child of the custom layout.
This is relatively straightforward programmatically, but more of an issue in XML.
Androids LayoutInflater logically interprets the nested levels in an XML file and builds the same structure in the hierarchy of Views it creates.
Your example XML defines 4 EditText Views as first tier children of FrageContainerView, but you want them to be created as second tier children of FrageContainerView sitting inside your LinearLayout. This would mean changing Androids LayoutInflater which is a core component of the whole Android system.
To do this programmatically you could do something like the following:
public class FrageContainerView extends LinearLayout {
private TextView objTextViewCaption;
private TextView objTextViewDescription;
private String caption;
private String description;
private LinearLayout objLayoutInner;
public FrageContainerView(Context context) {
this(context, null);
}
public FrageContainerView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize(context, attrs);
}
public FrageContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize(context, attrs);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public FrageContainerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize(context, attrs);
}
private void initialize(Context context, AttributeSet attrs) {
// Create your 3 predefined first tier children
// Create the Caption View
objTextViewCaption = new TextView(context);
// You can add your new Views to this LinearLayout
this.addView(objTextViewCaption)
// Create the Description View
objTextViewDescription = new TextView(context);
// You can also provide LayoutParams when you add any of your new Views if you want to
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
this.addView(objTextViewDescription, params);
// Create your inner LinearLayout
objLayoutInner = new LinearLayout(context);
objLayoutInner.setOrientation(VERTICAL);
// Add it
this.addView(objLayoutInner);
TypedArray a =
context.obtainStyledAttributes(attrs, R.styleable.options_frageContainerView, 0, 0);
caption = a.getString(R.styleable.options_frageContainerView_caption);
description = a.getString(R.styleable.options_frageContainerView_description);
a.recycle();
setOrientation(LinearLayout.HORIZONTAL);
setGravity(Gravity.CENTER_VERTICAL);
/**
* Oops! Only just spotted you're inflating your three predefined views
* here. It's fine to do this instead of programmatically adding them as I
* have above. Obviously they should only be added once, so I've commented out
* your version for the moment.
**/
// LayoutInflater inflater =
// (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// inflater.inflate(R.layout.view_fragecontainer, this, true);
//
// objLayoutInner = (LinearLayout) findViewById(R.id.linearlayout_inner);
// objTextViewCaption = (TextView) findViewById(R.id.textview_caption);
// objTextViewDescription = (TextView) findViewById(R.id.textview_description);
objTextViewCaption.setText(caption);
objTextViewDescription.setText(description);
}
}
/** Public method for adding new views to the inner LinearLayout **/
public void addInnerView(View view) {
objLayoutInner.addView(view);
}
/** Public method for adding new views to the inner LinearLayout with LayoutParams **/
public void addInnerView(View view, LayoutParams params) {
objLayoutInner.addView(view, params);
}
You would use this in your code with something like the following:
FrageContainerView fragContainerView = (FrageContainerView) findViewById(R.id.my_frag_container_view);
TextView newView = new TextView(context);
newView.setText("whatever");
fragContainerView.addInnerView(newView);
The other thing you could do is
1) Cache all current children and remove them all
ArrayList<View> nestedViews = ViewUtil.getAllChildren(this); removeAllViews();
2) Inflate the layout you have which already contains things
View myLayout = LayoutInflater.from(getContext()).inflate(R.layout.my_layout_with_stuff, null);
3) Add the cached views to the newly inflated layout and add it to the root back
for (View view : nestedViews) {
myLayout.<ViewGroup>findViewById(R.id.contentLayout).addView(view);
}
addView(myLayout);
I have a custom RelativeLayout and I inflate an xml res file in it.
This works fine if I use the custom layout in an xml file and set it as contentview, but if I try to add it in the code with new LocationItem(this) and addChild() the findViewById method always returns null in the constructor of the custom RelativeLayout.
Here is the code:
public class LocationItem extends RelativeLayout {
private String parcelType;
private int countIntoBox, countFromBox;
private RelativeLayout deliveryContainer, pickupContainer;
private TextView countPickup, countDelivery;
public LocationItem(Context context) {
this(context, null);
}
public LocationItem(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LocationItem(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
inflate(getContext(), R.layout.list_item_location, this);
deliveryContainer = (RelativeLayout) findViewById(R.id.rl_location_delivery_container);
pickupContainer = (RelativeLayout) findViewById(R.id.rl_location_pickup_container);
countPickup = (TextView) findViewById(R.id.tv_location_pickup_count);
countDelivery = (TextView) findViewById(R.id.tv_location_delivery_count);
countPickup.setOnClickListener(getShowNumberPickerListener());
countDelivery.setOnClickListener(getShowNumberPickerListener());
}
private OnClickListener getShowNumberPickerListener() {
return new OnClickListener() {
#Override
public void onClick(View view) {
showNumberPickerDialog(view);
}
};
} ...
}
Add custom view in activity
mRootLayoutLocations.addView(new LocationItem(this));
The view is inflated correctly, because I can see it, but when I try to access a view inside the custom view the app crashes with a NullPointerException.
Ok i inflated the view into a View(holder)
View v = inflate(getContext(), R.layout.list_item_location, this);
and then access the views via v.findViewById. Now it's working.
Code:
View v = inflate(getContext(), R.layout.list_item_location, this);
deliveryContainer = (RelativeLayout) v.findViewById(R.id.rl_location_delivery_container);
pickupContainer = (RelativeLayout) v.findViewById(R.id.rl_location_pickup_container);
countPickup = (TextView) v.findViewById(R.id.tv_location_pickup_count);
countDelivery = (TextView) v.findViewById(R.id.tv_location_delivery_count);
countPickup.setOnClickListener(getShowNumberPickerListener());
countDelivery.setOnClickListener(getShowNumberPickerListener());
You need to be using appropriate constructors, not overloading them.
public class LocationItem extends RelativeLayout {
private String parcelType;
private int countIntoBox, countFromBox;
private RelativeLayout deliveryContainer, pickupContainer;
private TextView countPickup, countDelivery;
public LocationItem(Context context) {
super(context);
init(context, null, 0);
}
public LocationItem(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs, 0);
}
public LocationItem(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs, defStyle);
}
private void init(Context context, AttributeSet attrs, int defStyle) {
inflate(getContext(), R.layout.list_item_location, this);
deliveryContainer = (RelativeLayout) findViewById(R.id.rl_location_delivery_container);
pickupContainer = (RelativeLayout) findViewById(R.id.rl_location_pickup_container);
countPickup = (TextView) findViewById(R.id.tv_location_pickup_count);
countDelivery = (TextView) findViewById(R.id.tv_location_delivery_count);
countPickup.setOnClickListener(getShowNumberPickerListener());
countDelivery.setOnClickListener(getShowNumberPickerListener());
}
private OnClickListener getShowNumberPickerListener() {
return new OnClickListener() {
#Override
public void onClick(View view) {
showNumberPickerDialog(view);
}
};
}
...
}
I fear you must inflate the view instead of finding it from nowhere. The method
findindViewById(int Id)
must be called within the Actvity's onCreate or with a view within which the child view/widget you are trying to find is resides in.
If all of the child views resides in a single xml file(within single parent root)
View rootView=(View) LayoutInflater.from(context).inflate(R.layout.list_item_location);
pickupContainer = (RelativeLayout) rootview.findViewById(R.id.rl_location_pickup_container);
you can try this. init views in the method onFinishInflate().This method will be called called as the last phase of inflation, after all child views have been added.So you can avoid NPE.
this should work
public LocationItem(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this = inflate(getContext(), R.layout.list_item_location,null);
...