How to create a fragment with a list without ListFragment - android

There's very little literature on this topic, and google's documents don't account for the possibility of customization (listviewanimation) of the fragment's list using ListFragment extension. Therefore, I'm going to ask this question, and then answer it as best as possible, because I also want 50 reputation points so I can finally thank great explainers on this website through comments.
For the purpose of this comment, I will have components from the listviewanimation lib laced in:
https://github.com/nhaarman/ListViewAnimations
Answer:
We will need to set up 4 components to have a proper fragment with a listview component
The Activity Creating the fragment through the activity's fragment manager.
The Fragment class which will be pretty basic fragment stuff, it will have the listview, and it will link that listview with an arrayadapter.
The Adapter class which for our purposes will only handle strings.
WITHIN THE ADAPTER CLASS the final fourth component will be a viewholder class which will allow the rows within the list to be created faster, since each row's individual components will be wrapped up in a class that will allow for quicker object instantiation.
Ok so, first will be the code for the activity, this code can be called by a button click or some other event. When the event happens, the fragment manager will be created, and that fragment manager will create a transaction which is a fancy way of saying, the manager will communicate between the activity and the newly formed fragment to get everything set up properly.
Here's the code that should be placed in your activity where the event occurs:
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
GenericFragment fragment = new GenericFragment();
fragmentTransaction.add(R.id.pager, fragment);
//Replace R.id.pager with the view that you want your fragment to go in.
fragmentTransaction.commit();
That's it! Not so bad, is it? Now let's move on to the GenericFragment class, which you can create a different name of course. I won't be posting all the code for this, but I'll step through everything you need for a fragment class that has a listview:
Have your Fragment class extend Fragment
Have an empty constructor for this class (google requires it... -__- )
Create a newInstance method which will handle the passing of data from the activity to the fragment when a 'new instance' of the fragment is created from the activity:
I'll help you with this one:
public static GenericFragment newInstance(String StuffYouWantGetsPassedFromActivityToFragment) {
GenericFragment GenericFragment = new GenericFragment();
Bundle args = new Bundle();
GenericFragment.setArguments(args);
return GenericFragment;
}
Again not so bad, right? We're still not done, we still need to override onCreateView and onCreate, then we'll be done with this simple step!
Ok for onCreateView:
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.generic_fragment_layout, container, false);
addGoalButton = (Button) view.findViewById(R.id.btn_newRow); //Created for testing purposes
lv = (ListView) view.findViewById(R.id.GenericListView);
addGoalButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) { //Created for testing purposes
genericAdapter.add("Goal");
genericAdapter.notifyDataSetChanged();
}
});
lv.setAdapter(genericAdapter);
return view;
}
That above code may seem like a monstrosity, and you're right! The high level overview is that you're getting the layout file that you want the fragment to look like. From that layout file, you're getting the listview and creating a variable to hold it in. Then you're calling that listView's 'setAdapter' method to add the next step, the adapter class. For testing purposes, I added that button, so that you can mentally extend this tutorial l8er. (delete all button code if you'd like just a list)
Ok, one last step in the fragment class: Overriding OnCreate!
The OnCreate method is where you want to instantiate all your private variables like the genericAdapter variable or anything that you'd like to use over the multiple parts of the Fragment class.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ArrayList<String> exampleItemList = new ArrayList<String>();
exampleItemList.add("item1");
exampleItemList.add("item2");
exampleItemList.add("item3");
exampleItemList.add("item4");
exampleItemList.add("item5");
exampleItemList.add("item6");
exampleItemList.add("item7");
exampleItemList.add("item8");
exampleItemList.add("item9");
exampleItemList.add("item10");
exampleItemList.add("item11");
exampleItemList.add("item12");
genericAdapter = new genericAdapter(getActivity(), 0, exampleItemList);
setHasOptionsMenu(true); // Allows the fragment to change the menu buttons
}
I added the example items to an arrayList to make this tutorial a bit more transparent about where data is coming from, and where it's going.
That's it! You're Fragment is done! It's almost over, I promise.
Let's knock these last two steps out together, creating a GenericAdapter class that extends ArrayAdapter and has a private inner ViewHolder class to wrap all the layout components in:
public class GenericAdapter extends ArrayAdapter<String>
LayoutInflater layoutInflater;
//Used to get the correct LayoutInflater to inflate each row item
public GenericAdapter(Context context, int resource, List<String> objects) {
super(context, 0, objects);
layoutInflater = layoutInflater.from(context);
}
/**
* #param position The position in the list to get the data for that row item.
* #param convertView The view for the row item that will be shown in the list.
* #param parent Having this object allows you to use the LayoutInflater for the parent.
* #return
*/
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final GenericViewHolder GenericViewHolder;
final String item = getItem(position);
if(convertView == null){
LinearLayout rootView = (LinearLayout) layoutInflater.inflate(R.layout.item_row, parent, false);
genericViewHolder = genericViewHolder.create(rootView);
rootView.setTag(genericViewHolder);
}
else{
genericViewHolder = (genericViewHolder) convertView.getTag();
}
genericViewHolder.textView.setText(item);
return genericViewHolder.rootView;
}
/**
* ViewHolder's allow for a single object to maintain a Goal row item, so that the row item
* doesn't have to create each individual component (textview layout etc.) each time the
* row object is created/recreated. Allows for fast scrolling with little latency.
*/
private static class GenericViewHolder {
public final LinearLayout rootView;
public final GripView gripView;
public final TextView textView;
private GoalViewHolder(LinearLayout rootView, GripView gripView, TextView textView) {
this.rootView = rootView;
this.gripView = gripView;
this.textView = textView;
}
public static GoalViewHolder create(LinearLayout rootView){
TextView textView = (TextView)rootView.findViewById(R.id.list_row_draganddrop_textview);
GripView gripView = (GripView)rootView.findViewById(R.id.list_row_draganddrop_touchview);
return new GenericViewHolder(rootView, gripView, textView);
}
}
}
That was again, a monstrosity, let's look at the high level overview, we created an adapter class, and a viewholder class for the adapter class to use. In the adapter's constructor we got a layoutinflater to help with inflating each row's item. Then, we created the getView method which get's called thousands of times in your app, because it handles making the each row appear when it's viewable by the user. The getView method sees if the view to be converted into a row is null or not. If it is, it will create a new data entry (a viewholder), but if it's not null, then that viewholder has already been created, so we get whatever was inside the viewholder already, so that we don't have to create a new row item.
phew! I don't expect you to understand any of that, but congrats if you do.
Ok so that's it. You should be set, and when your activity's event get's called, the fragment will show up in whatever view is containing the fragment. I'll post my xml files in my answer so that I can get those delicious upvotes (or not, I may be completely incorrect, but this worked for me!)
enjoy life, don't give up!

The activity xml, most of it is irrelevant to you the reader, but the container view for the fragment is pager:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<!--Navigation Drawer Still Under Construction-->
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- As the main content view, the view below consumes the entire
space available using match_parent in both dimensions. -->
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!-- android:layout_gravity="start" tells DrawerLayout to treat
this as a sliding drawer on the left side for left-to-right
languages and on the right side for right-to-left languages.
The drawer is given a fixed width in dp and extends the full height of
the container. A solid background is used for contrast
with the content view. -->
<ListView
android:id="#+id/left_drawer"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="singleChoice"
android:divider="#android:color/transparent"
android:dividerHeight="0dp"
android:background="#ffff"/>
</android.support.v4.widget.DrawerLayout>
<!--Navigation Drawer Still Under Construction-->
<!--Customizations on README at: https://github.com/astuetz/PagerSlidingTabStrip-->
<com.astuetz.PagerSlidingTabStrip
android:id="#+id/tabs"
android:layout_width="wrap_content"
android:layout_height="48dip"
app:pstsShouldExpand="true"
app:pstsIndicatorHeight="5dip"
app:pstsDividerPadding="0dip"
app:pstsDividerColor="#ff6d00"
app:pstsUnderlineColor="#ff5722"
app:pstsIndicatorColor="#ff5722"/>
<!--To scale the viewpager vertically, android:layout_above="#+id/[viewname]" -->
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/tabs"
tools:context=".MainActivity" />
</RelativeLayout>
The xml layout for the fragment:
<?xml version="1.0" encoding="utf-8"?>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="New Item"
android:id="#+id/btn_newItem"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<com.nhaarman.listviewanimations.itemmanipulation.DynamicListView
android:id="#+id/GenericListView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_above="#+id/btn_newGoal" />
The specific row item:
<?xml version="1.0" encoding="utf-8"?>
<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="wrap_content"
tools:ignore="UseCompoundDrawables">
<com.nhaarman.listviewanimations.itemmanipulation.dragdrop.GripView
android:id="#+id/list_row_draganddrop_touchview"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:color="#android:color/darker_gray"
android:paddingBottom="4dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingTop="4dp" />
<TextView
android:id="#+id/list_row_draganddrop_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-light"
android:gravity="center_vertical"
android:minHeight="48dp"
android:textColor="?android:attr/textColorSecondary"
android:textSize="20sp"
tools:ignore="UnusedAttribute" />
</LinearLayout>
The layout portion of the 2nd code snippet got cut off and SO wasn't agreeing with my ctrl K'ing, but the long and short of it, is that it doesn't matter, because the listview is there, so it doesn't matter whether you put it in a linear layout or a relative layout.
Good luck bro's happy coding

Related

Fragment Question, where to build buttons etc

I am using fragments to update a text view I have so when the person clicks a button the text view moves on to the next question. I'm not sure if I am doing the correct work in one fragment instead of the other. My current screen looks like this:
I will probably have to add some more buttons/widgets to this but should I be adding it into the XML for the fragment or the fragment container?
Here is XML for fragment actions:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/fragment_question_layout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".FragmentActions"
>
<!-- this is where fragments will be shown-->
<FrameLayout
android:id="#+id/question_container1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4"
android:scaleType="centerInside" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="#+id/questions_yes1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="#string/yes" />
<Button
android:id="#+id/questions_no1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="#string/no" />
</LinearLayout>
</LinearLayout>
And here is the fragment details:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/button_layout1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
tools:context=".FragmentDetails">
<!--Blank Fragment Layout-->
<TextView
android:id="#+id/questions_text_view1"
android:layout_width="match_parent"
android:layout_height="91dp"
android:gravity="center"
android:textAlignment="center"
/>
</FrameLayout>
Updated FragmentDetails
public class FragmentDetails extends Fragment {
private final String TAG = getClass().getSimpleName();
private List<Integer> mQuestionIds;
private int mListIndex;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Inflate the fragment layout
View rootView = inflater.inflate(R.layout.fragment_details, container, false);
//Get a reference to the textView in the fragment layout
final TextView textView = (TextView) rootView.findViewById(R.id.questions_text_view1);
if (mQuestionIds != null) {
textView.setText(mQuestionIds.get(mListIndex));
//Increment the position in the question lisy as long as index is less than list length
if (mListIndex < mQuestionIds.size() - 1) {
mListIndex++;
setmQuestionIds(QuestionList.getQuestions());
setmListIndex(mListIndex);
} else {
//end of questions reached
textView.setText("End of questions");
}
//Set the text resource to display the list item at that stored index
textView.setText(mQuestionIds.get(mListIndex));
}
else {
//Log message that list is null
Log.d(TAG, "No questions left");
}
//return root view
return rootView;
}
public void setmQuestionIds (List < Integer > mQuestionIds) {
this.mQuestionIds = mQuestionIds;
}
public void setmListIndex ( int mListIndex){
this.mListIndex = mListIndex;
}
}
Fragment Actions activity
public class FragmentActions extends AppCompatActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_actions);
Button yes = findViewById(questions_yes1);
// Only create new fragments when there is no previously saved state
if (savedInstanceState == null) {
//Create Question Fragment
final FragmentDetails fragmentDetails = new FragmentDetails();
fragmentDetails.setmQuestionIds(QuestionList.getQuestions());
yes.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//set the list of question Ids for the head fragent and set the position to the second question
//Fragment manager and transaction to add this fragment
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.question_container1, fragmentDetails)
.commit();
}
});
}
}
}
If your Buttons remain the same while the TextView changes, you may add your Buttons to the fragment container.
Remember that, your fragments will be presented inside the FrameLayout of the fragment container. You gotta keep your Buttons, outside the FrameLayout.
Or if you want to have different Buttons for different fragments (Questions, in your case), you can also add the Buttons to the fragments. But in that case, you gotta add them separately to each of the fragments.
I guess there's no right answer to your question. You could try different approaches.
Maybe you could implement the buttons in the fragment container, as #smmehrab pointed out. I see this as a more difficult solution, because when you click on an item from the container you can manage the views of the container, not the fragment's views. You would get NullPointer if I recall correctly. This happens because the context when the button is clicked in the fragment container is different than the context when clicking from within the fragment. So you should implement an interface on the fragment container that listens to clicks, and the fragment catches the click. You could do this, and I actually am doing it in my current app, but I have no choice.
You could instead use Motion Layout (which extends from Constraint Layout) as the root view of your fragment, instead of CardView. This way you could set all the fragment's views with a flat hierarchy (flat hierarchies improves rendering time, so that's an improvement, and you can use CardView as one child) and set the buttons right there, in the Motion Layout (remember, the motion layout would be the fragment's root view). You could set the click listener right there and implement animations between different textViews.
I'm sure there are plenty of other solutions, take this only as a contribution.
If you're unfamiliar with Motion Layout you can just google it, android official documentation about it is great.

itemSelected not fired in Xamarin.Android

I'm trying to develop a sign-up menu for a social app, that I'm working on. I would like the sign-up menu to consist of a PageViewer, which holds five fragments. The last three fragments contains a ListView, where the user can 'check' information about them selves. The XML is here:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:minWidth="25px"
android:minHeight="25px"
android:id="#+id/layoutSignupLists">
<TextView
android:text="Add something here"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:id="#+id/signupListDescription" />
<ListView
android:id="#+id/interestListView"
android:layout_below="#id/signupListDescription"
android:layout_height="match_parent"
android:layout_width="match_parent" />
</RelativeLayout>
This layout is inflated, when the last three fragments are created as is displayed correctly. I have subscribed a delegate to the itemSelected event in the ListView as seen below:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
//Inflate view and find content
View view = inflater.Inflate(Resource.Layout.signupFragLayout4, container, false);
interestListView = view.FindViewById <ListView>(Resource.Id.interestListView);
var desc = view.FindViewById<TextView>(Resource.Id.signupListDescription);
//Adds the description
desc.Text = GetString(Resource.String.profile_menu_edit_interests);
//Populate ListView
interestListView.Adapter = new ArrayAdapter<string>(Activity,
Resource.Layout.CheckedListViewItem, MainActivity.InfoNames[(int)InfoType.Interest]);
interestListView.ChoiceMode = ChoiceMode.Multiple;
interestListView.ItemSelected += (object sender, AdapterView.ItemSelectedEventArgs e) =>
{
if(!Interests.Contains(e.Position))
Interests.Add(e.Position);
else
Interests.Remove(e.Position);
};
return view;
}
When putting a break-point in the delegate I find that it's never called and thus the ListView reset upon swiping right or left.
How can I make the fragment 'hold on' to the information so that it's displayed every time the fragment is shown?
Place the information in the MainActivity. You can make a simple reference to it by putting this variable in your fragment class:
MainActivity mainActivity;
And in your OnCreateView() place this to initialize it:
mainActivity = (MainActivity)Activity;
Now if you have any public variables in the mainActivity, you can reference them in the fragment like:
textView.Text = mainActivity.VariableName;
Additionally, for putting the information back in the fragment, override the 'OnResume' method like so:
public override void OnResume()
{
base.OnResume();
//Code to fill in all the information in your fragment again. I.E:
textView.Text = mainActivity.VariableName;
}

Android Fragment only uses last value for edittext after second "refresh"

I've got a fragment which dynamially adds LinearLayouts with two imagebuttons and a edittext.
The Value from the edittext is stored in a .txt File and retrieved via the function retrievecounter.
This is the Layout which gets added:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="100" >
<ImageButton
android:id="#+id/imageView1"
android:layout_width="56dp"
android:layout_height="50dp"
android:layout_weight="5"
android:src="#drawable/ic_action_photo" />
<EditText
android:id="#+id/singelfirstet1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="85"
android:ems="10" >
<requestFocus />
</EditText>
<ImageButton
android:id="#+id/buttonDelete"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="10"
android:background="#android:color/transparent"
android:contentDescription="#string/button_delete_row_description"
android:src="#drawable/testi" />
</LinearLayout>
This is my fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
ScrollView mLinearLayout = (ScrollView) inflater.inflate(
R.layout.editlistsfirst, container, false);
operations = new ArrayList<String>();
operations = retrievecounter(0);
temp = (LinearLayout) mLinearLayout.findViewById(R.id.lineartest);
LayoutInflater inflater2 = (LayoutInflater) getActivity()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
for (int i = 0; i < operations.size(); i++) {
View tempr = (inflater2.inflate(R.layout.list_singlefirst,
container, false));
ImageButton bt = (ImageButton) tempr
.findViewById(R.id.buttonDelete);
EditText et = (EditText) tempr.findViewById(R.id.singelfirstet1);
et.setText(operations.get(i));
String show = et.getText().toString();
Log.w("Class",show);
bt.setOnClickListener(this);
temp.addView(tempr);
}
return mLinearLayout;
}
I navigate between the fragments via an actionbar with tabs.
This is my onTabselected function:
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
ft.replace(R.id.fragment_container, fragment);
}
On the first click the values from the .txt file are displayed correctly, but after i switch to a different tab/fragment and back to this fragment only the last value of my ArrayList gets displayed.
Strange however is that the Log.w displays the correct value for the edittext both times.
Any idea what causes this?
Thanks in advance
If i call my function of the button inside the linearlayout:
temp.removeView((View) v.getParent());
for all buttons till all layouts are gone and go to another tab and back it displays the correct values, but i've got no idea why. Maybe this is helpful in finding the problem.
edit2: I Found a solution, don't know if it is practical or why it works but it does:
public void onPause(){
super.onPause();
temp.removeAllViews();
}
After i added this it works. If someone could explain why that would be much appreciated.
One of the things to consider when dealing with Views like this is that they are only 'updated' when they are first created. When you switch between your tabs, the data will not be refreshed because it was A)Not recreated [refreshed] B)Not updated by an adapter.
When you removed all views on Pause(), you emptied your view, then it is recreated from your replace() function which is similar as creating a new view. Refreshing a layout by removing and re-adding everything every time you change something can become very computationally expensive which is why people will use an adapter to control their layouts.

ListView ghosting when using DrawerLayout with Fragments

My layout was working fine before I switched it to use fragments inside the DrawerLayout. After that the ListView in the main view ghosts a copy of the list when scrolling.
The ListView contents scroll, but a copy of the first page remains on screen. I've been logging debug messages and there is no second copy of listview created, and no second copy of data sent from the adapter.
The layout for the activity uses DrawerLayout with fragments before switching to fragments it worked fine.
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/drawer_layout"
android:layout_width="400dp"
android:layout_height="800dp">
<!-- The main content view -->
<fragment android:name="com.nosweatbudgets.receipts.ReceiptsFragment"
android:id="#+id/receipts_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<!-- The navigation drawer -->
<fragment android:name="com.nosweatbudgets.navigate.NavigateFragment"
android:id="#+id/navigate_fragment"
android:layout_width="300dp"
android:layout_height="match_parent"
android:layout_gravity="start"
/>
</android.support.v4.widget.DrawerLayout>
The fragment classes are each very simple.
ReceiptsFragment's onCreateView looks like this.
View view = inflater.inflate(R.layout.receipts, container, false);
_adapter = new ReceiptsAdapter(inflater);
ListView list = (ListView) view.findViewById(R.id.receipt_list);
list.setAdapter(_adapter);
_adapter.Refresh();
return view;
NavigateFragment's onCreateView is basically the same with a different adapter. It's the ReceiptsFragment that is ghosting while the NavigateFragment does not ghost. Yet they can basically the same approach.
View view = inflater.inflate(R.layout.navigate, container, false);
_navigate = new NavigateAdapter(inflater);
// setup the list view for the side
ListView list = (ListView) view.findViewById(R.id.left_drawer);
list.setAdapter(_navigate);
_navigate.Refresh();
return view;
For the above fragment classes I'm using the following layouts.
receipts.xml looks like this.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/receipt_list"/>
</RelativeLayout>
navigate.xml looks like this.
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<ListView
android:id="#+id/left_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice"
android:background="#EEEEEE"/>
</RelativeLayout>
The pull out navigate listview works just fine, but the main receipts listview has the ghosting problem.
I'm completely stumped as to what is causing this.
EDIT:
Here is how views are created in the ReceiptsAdapter.
public View getView(int position, View convertView, ViewGroup parent)
{
View view = convertView;
if (view == null)
{
view = _inflater.inflate(R.layout.receipt, null);
}
TextView txt_vendor = (TextView) view.findViewById(R.id.receipt_vendor);
TextView txt_category = (TextView) view.findViewById(R.id.receipt_category);
TextView category_btn = (TextView) view.findViewById(R.id.category_btn);
Model receipt = _receipts.get(position);
int vendor_id = receipt.getAsInteger("smart_vendor_id");
int category_id = receipt.getAsInteger("smart_category_id");
if (_vendors.indexOfKey(vendor_id) >= 0)
{
txt_vendor.setText(_vendors.get(vendor_id));
}
else
{
txt_vendor.setText("Select Vendor");
}
if (_categories.indexOfKey(category_id) >= 0)
{
String cat = _categories.get(category_id);
txt_category.setText(cat);
category_btn.setText(Category.ShortTitle(cat));
category_btn.setBackgroundColor(Category.Color(cat));
//txt_category.setBackgroundColor(c);
}
else
{
txt_category.setText("Select Category");
category_btn.setText("N/A");
}
((TextView) view.findViewById(R.id.receipt_amount)).setText("$" + receipt.getAsString("amount"));
((TextView) view.findViewById(R.id.receipt_payment)).setText(receipt.getAsString("payment"));
((TextView) view.findViewById(R.id.receipt_date)).setText(receipt.getAsString("receipt_date"));
return view;
}
EDIT:
This is how the Activity for the layout attaches the fragments via the onCreate method.
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// the layout uses fixed width/size, because the layout editor won't render match_parent.
// so I replace the layout parameters here with match_parent.
DrawerLayout layout = (DrawerLayout) getLayoutInflater().inflate(R.layout.main, null);
DrawerLayout.LayoutParams param = new DrawerLayout.LayoutParams(ViewGroup.LayoutParams
.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
layout.setLayoutParams(param);
setContentView(layout);
// configure this activity
StyleActionBar();
// Add the fragments
getSupportFragmentManager().beginTransaction()
.add(R.id.navigate_fragment, new NavigateFragment())
.add(R.id.receipts_fragment, new ReceiptsFragment())
.commit();
}
I mistakenly followed the fragments tutorial incorrectly, and created both a static fragment in the layout XML and also created a dynamic fragment for the same class at run-time.
The main layout used the <fragment> tag to create a fragment which is automatically attached at runtime via the android:name property.
This is what I have in my layout.xml
<fragment android:name="com.nosweatbudgets.receipts.ReceiptsFragment"
android:id="#+id/receipts_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
That was enough to create the fragment, but I mistakenly added code from the fragments tutorial in my activity as follows.
getSupportFragmentManager().beginTransaction()
.add(R.id.navigate_fragment, new NavigateFragment())
.add(R.id.receipts_fragment, new ReceiptsFragment())
.commit();
That added two additional fragment views to the activity at runtime. This resulted in two listviews for the same position.
What appeared to be a ghosting artifact was infact two fragments for the same class on top of each other. Removing the dynamic creation in onCreate resolved my problem.

Android: ListView with expanding LinearLayouts as items causes duplicates

I'm trying to write a test application that consists of a few fragments.
One fragment should contain a listView of all music artists from the device.
Each item of this list is a linearlayout starting with a TextView with the artist name and an empty linearlayout under it as follows:
The list is of this layout:
<ListView
android:id="#+id/artistsLists"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true" >
</ListView>
Each item is of the following layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="#+id/artistName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24sp"
android:text="" />
<LinearLayout
android:id="#+id/artistsAlbums"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</LinearLayout>
I'm populating the list using a SimpleCursorAdapter in the following way:
public class MusicTabFragment extends Fragment
{
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_music_tab,container,false);
Cursor artistsCursor = getActivity().getContentResolver().query(Audio.Artists.EXTERNAL_CONTENT_URI, new String[]{Audio.Artists.ARTIST,Audio.Artists._ID}, null, null,Audio.Artists.ARTIST);
SimpleCursorAdapter adapter = new SimpleCursorAdapter(view.getContext(), R.layout.music_artist_list_item_layout, artistsCursor, new String[]{Audio.Artists.ARTIST},new int[]{R.id.artistName},0 );
ListView lView = (ListView)view.findViewById(R.id.artistsLists);
lView.setAdapter(adapter);
lView.setOnItemClickListener(new OnItemClickListener()
{
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
((LinearLayout)view.findViewById(R.id.artistsAlbums)).removeAllViews();
Cursor albumsCursor = getActivity().getContentResolver().query(Audio.Artists.Albums.getContentUri("external", ((Cursor)parent.getItemAtPosition(position)).getLong(1)), new String[]{Audio.Albums.ALBUM, Audio.Albums._ID},null,null,null);
LinearLayout artistLayout = (LinearLayout)view.findViewById(R.id.artistsAlbums);
for(albumsCursor.moveToFirst();!albumsCursor.isAfterLast();albumsCursor.moveToNext())
{
View albumView = LayoutInflater.from(view.getContext()).inflate(android.R.layout.simple_list_item_1,artistLayout,false);
((TextView)albumView.findViewById(android.R.id.text1)).setText(albumsCursor.getString(0));
artistLayout.addView(albumView);
}
Log.d("POPULATE","populated again!");
albumsCursor.close();
}
});
return view;
}
}
This works just fine. when i click an artist name, the linearlayout populates with all of this artist album names.
the problem is, that once a linearLayout scrolls out of view, it shows again from the other edge of the view (PacMan Style) as if another list item's linearLayout was populated.
It happens every time the expanded layout goes out of sight. the funny part is that some times when scrolling back up, the linearLayout shows under a different artist name.
example
I'll be glad to hear how should I implement this fragment. But i will also like to know why this behavior is caused.
Thanks,
Maor.
I have found the solution here at stackoverflow.com
It appears that the view shouldn't hold any data, since it is being used for different data when i scroll back and fourth.
I think holding an external data structure to save each virtual view state is not nice programming. is there a way to keep this data anyway? (for this i will be looking now)

Categories

Resources