I'm a relatively new to programming and I'm trying to create an App in Android Studio that uses a dynamic layout. Of course I will need the created Views to be assigned an ID so that I can read and display specific data via the findViewbyId method. However the generateViewId() method seems to fail here. I've broken my code down to an easy example for better illustration:
public class MainActivity extends AppCompatActivity {
MultiAutoCompleteTextView mactv;
ArrayList<Integer> id_List = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout layout = (LinearLayout)findViewById(R.id.dynamicLayout);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
for (int i = 0; i < 5; i++) {
mactv = new MultiAutoCompleteTextView(MainActivity.this);
mactv.setLayoutParams(params);
mactv.generateViewId();
mactv.setHint("This is Number: " + i);
layout.addView(mactv);
id_List.add(mactv.getId());
}
Log.e("Path 1",id_List.toString());
}}
Here's the layout xml-file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="test.dynamiclayout.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/dynamicLayout"
android:orientation="vertical">
</LinearLayout>
</LinearLayout>
If I run this code, Logcat will show the ViewIds as [-1 -1 -1 -1 -1] and this certainly is the reason I'm having trouble with my actual code. I'm just puzzled as to what I did wrong here? I thought these few lines would be a straight forward thing...
Any help would be highly appreciated!
Per documentation generateViewId does this:
Generate a value suitable for use in setId(int)
As this value isn't set automatically you'll need to assign this value using mactv.setId(mactv.generateViewId).
Related
I am using PlaceHolderView library to show an Infinite List of contents like the Facebook Android app. I was using InfinitePlaceHolderView for it. It looks like this:
And here is how it looks like output
Now If the story is large then a user can click on the card view and it will take him to a new activity where there will be a detail of that story. For this, I need to add a Listener. But I could not find any documentation at all, how to implement it. The documentation shows that all those cards are dynamically created and not any of those cards have any id so that I can put a listener.
How to implement it?
So, what I did is given below:
In my feed.xml file which contain the above view, I set an id for the whole view. Then I used that id and used it ItemView.java from here:
feed.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:id="#+id/home_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/grey_100"
android:orientation="vertical"
android:weightSum="4"
tools:context="com.example.shamsad.tutionhub.activities.FeedActivity">
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
android:id="#+id/tbHome"
android:background="#color/colorPrimary"
app:titleTextColor="#color/grey_100">
</android.support.v7.widget.Toolbar>
<com.mindorks.placeholderview.InfinitePlaceHolderView
android:id="#+id/loadMoreView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
ItemView.java
#Click(R.id.rootView)
public void onCardClick() {
Intent intent = new Intent(mContext, DetailActivity.class);
intent.putExtra("title", mInfo.getTitle());
intent.putExtra("caption", mInfo.getCaption());
intent.putExtra("salary", mInfo.getSalary());
intent.putExtra("routine", mInfo.getRoutine());
intent.putExtra("time", mInfo.getTime());
Log.d("sajid", "onClick");
mContext.startActivity(intent);
}
So, in this onCardClick() method, the clickable id is set up by this code #Click(R.id.rootView). And that's all. It basically sets the whole card-view as a clickable thing.
And also thanks to the writer of this great library Janisher Ali. I contacted him and he helped a lot, and it worked.
This is a great library. To me much better than RecyclerView. You can try it out :)
In step three of the documentation you have an example of the Event Bind:
#Animate(Animation.ENTER_LEFT_DESC)
#NonReusable
#Layout(R.layout.gallery_item_big)
public class ImageTypeBig {
#View(R.id.imageView)
private ImageView imageView;
private String mUrl;
private Context mContext;
private PlaceHolderView mPlaceHolderView;
public ImageTypeBig(Context context, PlaceHolderView placeHolderView, String url) {
mContext = context;
mPlaceHolderView = placeHolderView;
mUrl = url;
}
#Resolve
private void onResolved() {
Glide.with(mContext).load(mUrl).into(imageView);
}
#LongClick(R.id.imageView)
private void onLongClick(){
mPlaceHolderView.removeView(this);
}
}
More information:
https://github.com/janishar/PlaceHolderView#step-3-create-item-class-to-bind-and-define-view-operations
I have a UI with some EditTexts in it and this set of edittexts can repeat number of times (not too much but 3 - 10 times max) based on the number of items in the list.
User can edit/modify/delete the item or edit the value of the edit texts. Currently I am doing this manually with "AddView/RemoveView", manually handling the states etc, however it is a lot of work as I have many scenarios like this.
We have a web app with the very same functionalities and we are using AngularJS to deal with all these, which, as you know is amazingly easy.
is there any closer way to bind the axml/xml view with a collection (may be an Observable collection and at least from the code behind) that will take care of collection changes as well as the individual field changes without me doing all this manually. In some scenarios I have to display images as well.
Also, I tried using a ListView, however it doesn't work as I would expect it to work.
is there any closer way to bind the axml/xml view with a collection (may be an Observable collection and at least from the code behind) that will take care of collection changes as well as the individual field changes without me doing all this manually.
The answer is no, there isn't. Android's views have to be bound to certain context/activity when they are created. They can't be isolated, so add/remove the EditTexts have to be implemented by yourself.
Currently, the closest way to your requirement is to create an ObservableCollection and listen for the CollectionChanged event and when CollectionChanged add/remove the view in your container:
[Activity(Label = "Demo", MainLauncher = true)]
public class MainActivity : Activity
{
Button btnAdd;
ObservableCollection<View> oc;
LinearLayout container;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
btnAdd = FindViewById<Button>(Resource.Id.btnAdd);
btnAdd.Click += BtnAdd_Click;
GenerateET(Resource.Id.container, this, 3);
}
private void BtnAdd_Click(object sender, System.EventArgs e)
{
EditText et = new EditText(this);
et.Text = "test";
et.LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
oc.Add(et);
}
public void GenerateET(int resId, Activity activity,int num)
{
//create an observable collection
oc = new ObservableCollection<View>();
container = activity.FindViewById<LinearLayout>(resId);
for (int i = 0; i < num; i++)
{
EditText et = new EditText(activity);
et.Text = "test";
et.LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.WrapContent);
container.AddView(et);
oc.Add(et);
}
oc.CollectionChanged += Oc_CollectionChanged;
}
private void Oc_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
for (int i = 0; i < e.NewItems.Count; i++)
{
//add the view manually
container.AddView((View)e.NewItems[i]);
}
}
}
}
Main.axml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</LinearLayout>
<Button
android:id="#+id/btnAdd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add EditText"/>
</LinearLayout>
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.
Hi I am working on writing my first usable android app. I have the following query , is there a way to populate values of a flied , based on value selected in a spinner.
e.g When country A is selected , 3 values are shown .
But when country B is selected, only 2 values are shown.
Is there a way to achieve this on Android screen? can someone provide some examples or point me in the right direction.
I think this example can help you not sure. I am creating a linear layout on which I will add all option. I am managing options in a hashmap. You can change according to your requirement.
Here is my main Activity:
public class MainActivity extends Activity {
HashMap <String, CheckBox> options = new HashMap<String, CheckBox>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int number_of_options = 2;
for (int i = 0; i < number_of_options; i ++) {
create_view("option " + i);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private void create_view(String option)
{
LinearLayout layout = new LinearLayout(getApplicationContext());
layout.setOrientation(LinearLayout.VERTICAL);
TextView text_option = new TextView(getApplicationContext());
text_option.setText(option);
CheckBox check_box = new CheckBox(getApplicationContext());
layout.addView(text_option);
layout.addView(check_box);
LinearLayout inner_layout = (LinearLayout) findViewById(R.id.linear_layout);
inner_layout.addView(layout);
options.put(option, check_box);
}
}
create_view will create options for you.
Here is my XML layout:
<Spinner
android:id="#+id/spinner1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginTop="28dp" />
<LinearLayout
android:id="#+id/linear_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="#+id/spinner1"
android:layout_below="#+id/spinner1"
android:orientation="horizontal" >
</LinearLayout>
I am simply creating a linear layout and adding options on it. If you are having any doubt then you may ask I will try to help.
I am not managing margin between options.
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!