It looks like a kind of wrong approach, but I'd still ask.
The task is, you have a layout xml that describes a composite widget (like Button + TextView). You'd like to make it re-usable, so you build a class like MyTextViewButtonWidget - it will expose its button text accessors and it will also do the same for text view:
public class MyTextViewButtonWidget extends LinearLayout {
...
void setButtonText(String text) { ... }
String getButtonText() { ... }
void setTextViewText(String text) { ... }
String getTextViewText() { ... }
...
}
Layout definition looks like this:
<LinearLayout ..........>
....button and text label here...
</LinearLayout>
The question is - how would you load this layout so that its root LinearLayout would be the LinearLayout part of MyTextViewButtonWidget?
Tried defining MyTextViewButtonWidget's ctor like this:
{
inflate(getContext(), R.layout.reusable_widget_layout, this);
}
But this loads reusable_widget_layout as a child to MyTextViewButtonWidget (that's not what I need).
Generally, the problem is:
You need to create a composite widget
You'd like to be able to define its layout with xml markup
You'd like it to load root's child widgets defined in xml as its child widgets (INSTEAD OF: load the whole hierarchy from xml as a single child of your reusable widget)
Replace:
<LinearLayout ..........>
....button and text label here...
</LinearLayout>
with:
<merge ..........>
....button and text label here...
</merge>
Related
I designed an action bar that has several variations:
As you can see this action bar always has a header and there may be action buttons.
I understand that I can just create five action bars and use them. But I want to create one component and reuse it everywhere in my application.
For example, in ReactJS I would just create one component ActionBar with some properties and reuse it where I need it, such as:
<ActionBar
isBackButton = true
text = "Screen name"
actionButton = "Add"
/>
or
<ActionBar
isBackButton = false
text = "Another screen name"
actionButton = "None"
/>
How do I achieve this?
Or should I not get steamed up and just create five different action bars?
Or create a universal action bar with text, right and left buttons, and then dynamically customize button icons, text and onClick actions in java code?
I definitely need your advice on what to do.
So the easiest way to do this is with a custom view. You'd create a layout file for it:
toolbar.xml (this is simplifies to just give the basic idea, you need to fill in all the styling data:
<LinearLayout>
<ImageView android:id="#+id/back"/>
<TextView android:id="#+id/text">
<ImageView android:id="#+id/icon>
</LinearLayout>
You'd then write a view
ToolbarView.java:
package com.example
public class ToolbarView extends View {
private ImageView back;
private TextView text;
private ImageView icon;
public ToolbarView(Context context, AttributeSet attrs) {
LayoutInflater.from(context).inflate(R.layout.toolbar, this)
back = findViewById(R.id.back)
text = findViewById(R.id.text)
//Same for all other views
}
public void setText(String text) {
text.setText(text)
}
public void setBackEnabled(boolean enabled) {
back.setVisibility(enabled ? VISIBLE : GONE)
}
public void setIcon(Drawable icon) {
if(icon == null) {
icon.setVisibility(GONE)
}
else {
icon.setVisibility(VISIBLE)
icon.setDrawable(icon)
}
}
After that, you can just put <com.example.Toolbar/> in your other layouts and it will embed the toolbar. You can get it via FindViewById and then call setText, setBackEnabled, etc.
In my XML file I have layout for my fragment which contains HorizontalScrollView like this:
<HorizontalScrollView
android:id="#+id/srollview_seasons_gallery
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="left">
</HorizontalScrollView>
In separate XML file called season_list_item I've made a schema how single item should look like:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/season_image"
android:layout_marginLeft="7dp"
android:layout_marginRight="7dp"
android:onClick="seasonItemClicked"/>
</RelativeLayout>
I add items dynamically with my java code like this:
for (int i=0; i<seasonsSize; i++) {
View vi = ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.season_list_item, null);
ImageView seasonImage = (ImageView) vi.findViewById(R.id.season_image);
//seasonImage.setId(i);
String imgUrl = response.body().getEmbedded().getSeasons().get(i).getImage().getMedium();
Picasso.with(getContext()).load(imgUrl).into(seasonImage);
seasonsLinearLayout.addView(vi);
}
seasonsScrollView.addView(seasonsLinearLayout);
And when I execute my onClick method:
public void seasonItemClicked(View view) {
}
I get error
java.lang.IllegalStateException: Could not find method seasonItemClicked(View) in a parent or ancestor Context for android:onClick attribute defined on view class android.support.v7.widget.AppCompatImageView with id 'season_image'
Uncommenting this line //seasonImage.setId(i); gives me error
android.content.res.Resources$NotFoundException: Unable to find resource ID #0x0`
Pictures are added to layout correctly, just like I want them to. But I cannot achieve to make them clickable. I also find seasonImage.setId(i) important in my case since I need number of the picture that was clicked for further actions.
Could you help me out how this should be approached?
The problem is android:onClick which call your method seasonItemClicked(). As many views you have with this attribute, they will all call this same method, but with the same ID android:id="#+id/season_image".
The setId method could be very annoying because you have to set an unique id. There is some method to generate it, so, for each image, you have to generate an unique ID, and if you set it dynamically, don't set it by xml.
However, assuming that your number of images can be variable, I'd prefer to add the click listener programmatically in the for-loop. That way, they will be related to the imageview clicked. As follows:
for (int i=0; i<seasonsSize; i++) {
...
ImageView seasonImage = (ImageView) vi.findViewById(R.id.season_image);
seasonImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// perform your actions, be aware that 'view' here, is the image clicked
}
}
...
seasonsLinearLayout.addView(vi);
}
And just remove the android:onclick attribute:
<ImageView
...
android:id="#+id/season_image"
android:layout_marginLeft="7dp"
android:layout_marginRight="7dp"/>
You are assigning conflicting ids, ids that are already assigned to other resources. The best way to generate ids for programmatically created views is to use View.generateViewId or reserve them in the res/values/ids.xml file.
In my Android application I have an activity featuring GoogleMaps. In case of notifications etc., I show a popup window. This works all quite fine. However, I also have another activity where I want to display the same information in the same way. The idea is to use the same popup window in the corresponding view (View2). The problem is that in this second activity/view the popup window does not appear and the code seems to crash at group.addView(popup, lp); (no explicit errors though; but I'm sure there's nothing null). I just don't see the essential difference between the two activities/views that might suggest why the popup windows works fine in the in the one view but not in the other. In the following I show the relevant code snippets.
Here is how I instantiate the popup in both activities. The only difference is the third parameter that refers to the ID of the parent view; a RelativeLayout in each case.
// GoogleMaps Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mapview);
[...]
this.popupPanel = new PopupPanel(this, R.layout.popup, R.id.relativeLayoutMap);
[...]
}
// View2 Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view2);
[...]
this.popupPanel = new PopupPanel(this, R.layout.popup, R.id.relativeLayoutView2);
[...]
}
This is the main code for initializing the popup and for displaying. Only the value ''parentViewID'' differs between the activities.
public PopupPanel(Activity activity, int layout, int parentViewID) {
this.activity = activity;
this.viewID = viewID;
ViewGroup parent = (ViewGroup) this.activity.findViewById(parentViewID);
this.popup = activity.getLayoutInflater().inflate(layout, parent, false);
}
public void show(boolean alignTop) {
RelativeLayout.LayoutParams lp=new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT,
RelativeLayout.LayoutParams.WRAP_CONTENT
);
lp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
ViewGroup group = (ViewGroup) this.activity.findViewById(this.viewID);
group.addView(popup, lp); // this 'crashes' for the View2 activity
group.invalidate();
}
Finally, here are the snippets of the corresponding layouts. In both cases I refer to a RelativeLayout where I want to place my popup.
<? xml version="1.0" encoding="utf-8"?>
<LinearLayout [...]>
<RelativeLayout [...] android:id="#+id/relativeLayoutMap">
<com.google.android.maps.MapView [...]/>
</RelativeLayout>
</LinearLayout>
For activity/view View2:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout [...] android:id="#+id/relativeLayoutView2">
<LinearLayout [...] >
...
</LinearLayout>
<LinearLayout [...] >
...
</LinearLayout>
<ScrollView [...] >
<LinearLayout [...] >
...
</LinearLayout>
</ScrollView>
</RelativeLayout>
Any hints are much appreciated! I know that has been addressed in several question, but my 'problem' is that it basically works. Just only in one activity/view, and not in another. It seems that I miss something rather stupid here.
I figured out the difference, and as expected that it was something rather stupid: I forgot to put the code that displays the panel not within the runOnUiThread. I reallt should have know better by now.
runOnUiThread(new Runnable() {
public void run() {
// code for showing the popup
}
});
One side question: I got on the right track when I put the addView(...) call within a try/catch-block. Without it just hangs up there without throwing an error (in LogCat; I'm using Eclipse). Is there a way that such errors are shown by default? Thanks!
basically I want to encapsulate a simple component from code that I already have.
Basically it's a LinearLayout with buttons inside. These buttons will make changes to a ListView, and there is also some other small stuff that it will do.
Currently I have a XML layout with those, and I programmatically setup everything else: the buttons, the interaction between the list and the other small stuff.
Obviously I thought to myself, let's encapsulate this.
I started out trying to extend the LinearLayout and adding the buttons.
Already I have no idea how to inflate the buttons to add to the view
What method do I override to create this buttons just before the view gets created without messing with the measures and inflations, etc.
I've looked around but the custom components I see are either completely new components or components that simply add small functionality to the custom ones.
Is there some guidelines for doing this?
Good tutorials/examples?
Any help is appreciated. Thanks !
EDIT:
Okay, here is a little more specific stuff.
Basically I want to create a View that holds filter buttons for a ListView. This will be used in different places with different filters, so I need flexibility for the buttons.
Basically I'd like to do something like this:
CustomView view = new CustomView(activity);
view.addButton("Lala", new OnFilterClickListener {
onClick(ListView list, View v) {
// Do the filtering
}
});
mListView.addHeaderView(view);
I want the view to adapt it's weights for showing the buttons, show the user which filter is active, stuff like that.
But I still don't really know how to make those dynamically added buttons appear, where do I generate them, how to inflate them and stuff like that.
public class myLayout extends LinearLayout {
//...
public void addButton(String text, OnClickListener listener) {
Button newButton = new Button(mContext);
newButton.setText(text);
newButton.setOnClickListener(listener);
//Say we want the weights to be equal
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.Fill_PARENT, 1);
addView(newButton, params);
}
//...
}
You can even do something to the view before dispatching the click like this:
public class myLayout extends LinearLayout {
//...
public void addButton(String text, final OnClickListener listener) {
Button newButton = new Button(mContext);
newButton.setText(text);
newButton.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
//do whatever you want
//like change background of button or something
//finally
listener.onClick(v);
}
});
//Say we want the weights to be equal
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0,LinearLayout.LayoutParams.Fill_PARENT, 1);
addView(newButton, params);
}
//...
}
Other questions say that the style cannot be set programmatically, but a View can be initialised with a style such as when it is loaded from XML.
How can I initialise a View with a particular style programmaticly (not in XML)? I tried using View(Context context, AttributeSet attrs, int defStyle), but I don't know what to parse in for the second argument. Passing in null results in the View not being displayed
I'm having the same problem, but haven't found any practical way to directly set a style programmatically, so far. I would like to populate my screen with a lot of widgets, of a given type, let's say buttons. It is impractical to define them all in the layout file. I would like to create them programmatically, but I would also like to define their style in a style xml file.
The solution I have devised consists in defining just one of those widgets in the layout file, create all the others programmatically, and clone the style info from the first one to the other ones.
An example follows.
In the style file, define the style for your buttons. For example:
<style name="niceButton">
<item name="android:layout_width">160dip</item>
<item name="android:layout_height">60dip</item>
<item name="android:gravity">center</item>
<item name="android:textSize">18dip</item>
<item name="android:textColor">#000000</item>
</style>
Then subclass class "Button", by deriving a class "NiceButton". Define the constructor that will be needed by the inflater:
public NiceButton(Context context, AttributeSet attrs) {
super(context, attrs);
}
Then define another constructor, which purpose is to clone an existing button:
public NiceButton(int id, NiceButton origButton) {
super(origButton.getContext());
setId(id);
setLayoutParams(origButton.getLayoutParams());
setGravity(origButton.getGravity());
setPadding(origButton.getPaddingLeft(),
origButton.getPaddingTop(),
origButton.getPaddingRight(),
origButton.getPaddingBottom());
setTextSize(TypedValue.COMPLEX_UNIT_PX, origButton.getTextSize());
setTextColor(origButton.getTextColors());
// ... also copy whatever other attributes you care about
}
In your layout file, define just the first one of your buttons. Suppose for example that you want to put your buttons in a table:
<TableLayout android:id="#+id/button_table"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TableRow android:id="#+id/button_row_0">
<com.mydomain.mypackage.NiceButton
style="#style/niceButton" android:id="#+id/button_0" />
<!-- More rows/buttons created programmatically -->
</TableRow>
</TableLayout>
Notice that the full qualified name of the widget class is used; obviously, you will have to replace com.mydomain.mypackage with the actual package name.
In your activity, you may want to define an array which is going to hold a reference to all of the buttons, and a common listener to be called when any of the buttons is pressed:
NiceButton[] mButtonViews = new NiceButton[10];
private View.OnClickListener mNiceButtonClickListener = new View.OnClickListener() {
public void onClick(View view) {
int i = view.getId();
mButtonViews[i].setText("PRESSED!");
}
};
Notice how the view id is used as an index in the array of buttons. So you will need your buttons to have an id from 0 to n-1.
Finally, here is the way you can create your buttons in the onCreate method:
// Retrieve some elements from the layout
TableLayout table = (TableLayout)findViewById(R.id.button_table);
TableRow row = (TableRow)findViewById(R.id.button_row_0);
NiceButton origButton = (NiceButton)findViewById(R.id.button_0);
// Prepare button 0
origButton.setId(0);
origButton.setText("Button 0");
origButton.setOnClickListener(mNiceButtonClickListener);
mButtonViews[0] = origButton;
// Create buttons 1 to 10
for (int i = 1; i < 10; i++) {
if (i % 2 == 0) {
row = new TableRow(this);
table.addView(row);
}
NiceButton button = new NiceButton(i, origButton);
button.setText("Button " + i);
button.setOnClickListener(mNiceButtonClickListener);
mButtonViews[i] = button;
row.addView(button);
}
Here's how the screen appears after you have pressed some buttons:
Well, there's some code involved, but in the end you can create as many widgets you want programmatically, and still have their attributes defined as a style.
If you want to style a view you have 2 choices: the simplest one is to just specify all the elements in code:
button.setTextColor(Color.RED);
button.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18);
The other option is to define the style in XML, and apply it to the view. In the general case, you can use a ContextThemeWrapper for this:
ContextThemeWrapper newContext = new ContextThemeWrapper(baseContext, R.style.MyStyle);
button = new Button(newContext);
To change the text-related attributes on a TextView (or its subclasses like Button) there is a special method:
button.setTextAppearance(context, R.style.MyTextStyle);
This last one cannot be used to change all attributes; for example to change padding you need to use a ContextThemeWrapper. But for text color, size, etc. you can use setTextAppearance.
AttributeSet contains the list of attributes specified in xml (ex. layout_width, layout_height etc).
If you are passing it as null, then you should explicitly set the height/width of view.