I am trying to build a custom view in Android that shows an image, and some text fields (non-editable).
I started by extending the RelativeLayout class for my custom view.
In the constructor of my custom view I created an ImageView and a TextView, and added them to the layout.
The TextView is loaded immediately, but the ImageView is loaded on a different thread and the bitmap is populated through a handler.
Once the bitmap is loaded, the ImageView is overlapping the TextView. The TextView is supposed to be on the "right-of" the ImageView, but this adjustment is not happening automatically.
I tried using customView.invalidate(), but that did not help.
This problem is not there when the same components are declared via XML.
Any help in resolving would be appreciated. Thanks
public class MyView extends RelativeLayout {
private TextView productName;
private ImageView productImage;
public MyView(Context context) {
super(context);
initHandler();
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initHandler();
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initHandler();
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
initHandler();
}
private void initHandler() {
handler = new Handler() {
#Override
public void handleMessage(Message msg) {
// implement logic to show content here
productName.setText(currentProduct.getTitle());
productImage.setImageBitmap(currentProduct.getImageBitmap());
MyView.this.invalidate();
}
};
productImage = new ImageView(getContext());
RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
layout.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
layout.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
productImage.setLayoutParams(layout);
//keep the textview to right of imageview
productName = new TextView(getContext());
layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
layout.addRule(RelativeLayout.RIGHT_OF, productImage.getId());
layout.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
productName.setLayoutParams(layout);
productName.setTextColor(Color.parseColor("#ffffff"));
this.addView(productImage);
this.addView(productName);
//logic to load content on a new thread goes here
}
}
XML in which I included the custom view:
<RelativeLayout 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">
<com.mycompany.MyView
android:background="#android:color/black"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</RelativeLayout>
you need add an id to your View in xml
<com.mycompany.MyView
android:id="#+id/myView"
android:background="#android:color/black"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
and in your java initilize it like
ImageView im = (ImageView)findViewById(R.id.myView);
try that
Related
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?
I am adding ImageView dynamically inside a RelativeLayout programmatically but I also need to attach remove icon/button with onClick handler with those dynamic ImageViews. So that if I click on any of the delete icon related to that dynamic image view will be deleted.
Here is my code where I am adding dynamic image views inside the layout:
RelativeLayout rl = (RelativeLayout) findViewById(R.id.rl);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT);
final ImageView iv = new ImageView(this);
iv.setId(id);
iv.setPadding(2, 2, 2, 2);
iv.setImageBitmap(BitmapFactory.decodeResource(
getResources(), (int)iconTable[id]));
iv.setScaleType(ImageView.ScaleType.MATRIX);
iv.setLayoutParams(lp);
rl.addView(iv);
I'd recommend to create a custom component. Something like this:
public class DeletableImageView extends LinearLayout {
private ImageView mImage;
private Button mButton;
DeletableImageListener mListener
public DeletableImageView(Context context) {
super(context);
init(context);
}
public DeletableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public DeletableImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
setOrientation(LinearLayout.VERTICAL);
inflate(context, R.layout.deletable_image, this);
initViews();
}
private void initViews() {
mImage= (ImageView) findViewById(R.id.image);
mButton= (Button) findViewById(R.id.delete_button);
mButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
if (mListener != null) {
mListener.deleteMe(DeletableImageView.this);
}
}
}
}
public void setListener(DeleteableImageListener listener) {
mListener = listener;
}
public interface DeletableImageListener {
void deleteMe(DeletableImageView me);
}
public void setImage(Drawable drawable) {
mImage.setBackground(drawable);
}
}
R.layout.deletable_imageis a normal xml-layout with <merge> as root element (since the DeletableImageView already is a LinearLayout. Within the merge-Tag you have the ImageView and the Button.
<merge xmlns="...">
<ImageView android:id="#+id/image" .../>
<Button android:id="#+id/delete_button" .../>
</merge>
In your code you don't add an ImageView, but your custom component and set the Listener as callback, so you can remove the custom component again.
RelativeLayout rl = (RelativeLayout) findViewById(R.id.rl);
final DeletableImageView div = new DeletableImageView(this);
div.setImage(...); // add this method to DeletableImageView
div.setListener(this);
// either implement these in your custom component or directly set it in the <ImageView> in R.layout.deletable_image
div.setScaleType(...);
div.setPadding(2, 2, 2, 2);
rl.addView(div);
The last step is to implement deleteMe(DeletableImageView me). You have to hold a reference to your RelativeLayout and it should work by just calling rl.removeView(me);
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);
How can I set margins in class extends from RelativeLayout?
I have tried this but it doesn't work:
public class MyRelativeLayout extends RelativeLayout {
int margin = 50;
public MyRelativeLayout(Context context) {
super(context);
setLook();
}
public MyRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setLook();
}
public MyRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setLook();
}
private void setLook() {
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.setMargins(margin, margin, margin, margin);
setLayoutParams(params);
}
}
How should I do this?
Update:
Usage of this view:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<info.korzeniowski.widget.MyRelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center" >
//content
</info.korzeniowski.widget.MyRelativeLayout>
</ScrollView>
A custom View should NEVER define its own margins. Margins are purely used for layouting and you cannot reliably use them to design your custom View.
You can essentially replicate the effect margins have without any of the problems that come with margins by using paddings and a child View:
public class MyRelativeLayout extends RelativeLayout {
private final int padding;
public MyRelativeLayout(Context context) {
super(context);
// Use 50 dip instead of 50 pixels
this.padding = LayoutHelper.dpToPixel(context, 50);
setLook();
}
public MyRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
// Use 50 dip instead of 50 pixels
this.padding = LayoutHelper.dpToPixel(context, 50);
setLook();
}
public MyRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// Use 50 dip instead of 50 pixels
this.padding = LayoutHelper.dpToPixel(context, 50);
setLook();
}
private void setLook() {
setPadding(this.padding, this.padding, this.padding, this.padding);
final View innerView = ...;
final LayoutParams innerViewParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
addView(innerView, innerViewParams);
}
}
The View called innerView should contain all the content you want to display in your custom View.
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);
}
}