Adding views programmatically in Android - java

Is it possible to add views to a different layout than the one called in setContentView() in the OnCreate()?
Im trying to use Zylincs ViewPager (found at http://www.zylinc.com/blog-reader/items/viewpager-page-indicator.html) and I have that part setup and working great.
What this leaves me with is 4 layouts. 1 is main.xml, and the other three are main_pg0.xml, main_pg1.xml, and main_pg3.xml
The three pages are where the content of each "page" is where main.xml contains the page viewer.
In the onCreate im using setContentView(main.xml).
What im trying to do now is be able to add textViews programically to some of the pages.
What I have right now is:
LayoutInflater factory = getLayoutInflater();
View layout = factory.inflate(R.layout.main_pg0, null);
View linearLayout = layout.findViewById(R.id.linearLayout);
TextView valueTV = new TextView(this);
valueTV.setText("hallo hallo");
valueTV.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT));
((LinearLayout) linearLayout).addView(valueTV);
This is as far as I've been able to get, which does not add the textview at all.
----EDIT----
Hoping to clarify a little more what im trying to do
Heres my code
Main.java
public class Main extends FragmentActivity implements PageInfoProvider {
public GlobalVars globalVars;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
globalVars = (GlobalVars) getApplicationContext();
MyPagerAdapter adapter = new MyPagerAdapter();
ViewPager myPager = (ViewPager) findViewById(R.id.viewpager);
myPager.setAdapter(adapter);
myPager.setCurrentItem(0);
ViewPagerIndicator indicator = (ViewPagerIndicator) findViewById(R.id.indicator);
myPager.setOnPageChangeListener(indicator);
indicator.init(0, adapter.getCount(), this);
Resources res = getResources();
Drawable prev = res.getDrawable(R.drawable.indicator_prev_arrow);
Drawable next = res.getDrawable(R.drawable.indicator_next_arrow);
indicator.setArrows(prev, next);
}
private class MyPagerAdapter extends PagerAdapter {
public int getCount() {
return 3;
}
public Object instantiateItem(View collection, int position) {
LayoutInflater inflater = (LayoutInflater) collection.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
int resId = 0;
switch (position) {
case 0:
resId = R.layout.main_pg0;
LoadVehicles();
break;
case 1:
resId = R.layout.main_pg1;
break;
case 2:
resId = R.layout.main_pg2;
break;
}
View view = inflater.inflate(resId, null);
((ViewPager) collection).addView(view, 0);
return view;
}
#Override
public void destroyItem(View arg0, int arg1, Object arg2) {
((ViewPager) arg0).removeView((View) arg2);
}
#Override
public void finishUpdate(View arg0) {
// TODO Auto-generated method stub
}
#Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == ((View) arg1);
}
#Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
// TODO Auto-generated method stub
}
#Override
public Parcelable saveState() {
// TODO Auto-generated method stub
return null;
}
#Override
public void startUpdate(View arg0) {
// TODO Auto-generated method stub
}
}
public String getTitle(int position) {
String title = "--Untitled--";
switch (position) {
case 0:
title = "Page 0";
break;
case 1:
title = "Page 1";
break;
case 2:
title = "Page 2";
break;
}
return title;
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<com.zylinc.view.ViewPagerIndicator
android:id="#+id/indicator"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#EAEAEA"
android:paddingBottom="8dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:paddingTop="8dp" />
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#ffeaeaea" >
<View
android:layout_width="fill_parent"
android:layout_height="1dp"
android:layout_alignBottom="#+id/current"
android:background="#C5C5C5" />
<ImageView
android:id="#+id/current"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerInParent="true"
android:src="#drawable/indicator_current" />
</RelativeLayout>
<android.support.v4.view.ViewPager
android:id="#+android:id/viewpager"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
main_pg0.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#FFFFFF"
android:orientation="vertical"
android:id="#+id/linearLayout" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:text="main_pg0.xml"
android:textColor="#0C0C0C"
android:textSize="18sp" />
</LinearLayout>
What im trying to do is add a text view programmatically to main_pg0.xml ONLY, while leaving the other 2 pages alone

You haven't explicitly mentioned whether you're feeding Fragments to the ViewPager, but since you're working from the Zylinc implementation, I'm going to assume you are. As a matter of fact, I'll just use the provided example code to show you the idea.
The key to understanding how to realise what you're after is to get that the pages in the ViewPager are self-contained units in the form of Fragments. They are reusable, have their own life cycle, and can handle their own layouts. Hence, if you want to make runtime modifications to the layout of the pages, you will want to do this inside the Fragment(s) that make up these pages.
The following method can be found in the example code's ItemFragment class. I've simply added the TextView from your own code snippet to it. For demonstration purposes, I did hide the ListView that used to fill up all the space in the date_fragment.xml layout file.
#Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.date_fragment, container, false);
View tv = v.findViewById(R.id.text);
((TextView)tv).setText(readableDateFormat.format(date));
// changes below
TextView valueTV = new TextView(getActivity());
valueTV.setText("hallo hallo");
valueTV.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT));
((ViewGroup)v).addView(valueTV);
return v;
}
That will give you the "hallo hallo" text (you're a Dutchie? ;)) directly underneath the date in the page:
By the way, you might want to consider switching to Jake's ViewPagerIndicator. Although I haven't closely looked at the one you're currently using, Jake's seems more flexible to me and cleaner to work with. Up to you of course.
Edit: Okay, so you're not actually using fragments in the ViewPager, but are rather feeding it the layouts. That's fine, although it'll probably become harder to manage when the pages get more complex. Anyways, key to changing the page's layouts at runtime is still to do it whenever you build/inflate it. When using fragments, that would be in the overridden method, as shown above. In your case, however, you'll need to do it directly in instantiateItem(...).
I didn't actually type this out in an IDE, so please mind and minor syntactical errors.
public Object instantiateItem(View collection, int position) {
LayoutInflater inflater = (LayoutInflater) collection.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = null;
switch (position) {
case 0:
view = inflater.inflate(R.layout.main_pg0, null);
TextView valueTV = new TextView(getActivity());
valueTV.setText("hallo hallo");
valueTV.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT));
((ViewGroup)view).addView(valueTV);
break;
case 1:
view = inflater.inflate(R.layout.main_pg1, null);
// or perhaps better for readability: build the layout in a different method, passing in the root
buildSecondPageLayout(view);
break;
case 2:
view = inflater.inflate(R.layout.main_pg2, null);
buildThirdPageLayout(view);
break;
}
return view;
}

You have to add view in main view which is inflated layout.So just add one line.
LayoutInflater factory = getLayoutInflater();
View layout = factory.inflate(R.layout.main_pg0, null);
View linearLayout = layout.findViewById(R.id.linearLayout);
TextView valueTV = new TextView(this);
valueTV.setText("hallo hallo");
valueTV.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT));
((LinearLayout) linearLayout).addView(valueTV);
layout.addView(linearLayout);

Related

Elements inside viewpagers` fragment

I have a problem. I have a viewpager with 3 fragments inside. In first fragment i have some ImageViews.
First of all how make that imageviews visible with timer? I used thise code below but i have error which looks like: variable 'mImageView' is accessed from within inner class, needs to be declared class.
mImageView.setVisibility(View.INVISIBLE);
mImageView.postDelayed(new Runnable() {
public void run() {
mImageView.setVisibility(View.VISIBLE);
}
}, 5000);
How can i solve this problem?
Second I tried to move that elements (ImageViews) by X values when user start scrolling from first fragment to next fragment. It works but when i go to last 3-d fragment app crash. So why it happen?!
MainActivity.java
pager.setPageTransformer(false, new ViewPager.PageTransformer() {
#Override
public void transformPage(View page, float position) {
// transformation here
final float normalizedPosition = Math.abs(Math.abs(position) - 1);
page.setAlpha(normalizedPosition);
int pageWidth = page.getWidth();
if (position < -1) { // [-Infinity,-1)
// This page is way off-screen to the left.
page.setAlpha(0);
} else if (position <= 1) { // [-1,1]
ImageView mImageView = (ImageView)findViewById(R.id.imageView2);
mImageView.setTranslationX((float) (-(1 - position) * 1.7 * pageWidth));
mImageView.setVisibility(View.INVISIBLE);
mImageView.postDelayed(new Runnable() {
public void run() {
mImageView.setVisibility(View.VISIBLE);
}
}, 5000);
// The 0.5, 1.5 values you see here are what makes the view move in a different speed.
// The bigger the number, the faster the view will translate.
// The result float is preceded by a minus because the views travel in the opposite direction of the movement.
}
else{ // (1,+Infinity]
// This page is way off-screen to the right.
page.setAlpha(0);
}
}
});
Third: Is it possible to make move elements by circle when user scroll. Need any help!
For your first question, as I said in the comment, you need to make the mImageView variable final
final ImageView mImageView = (ImageView)findViewById(R.id.imageView2);
Then, the null pointer exception, is probably caused (as Blackbelt said), because you're using the activity's findViewById method, and probably the imageView you need is in the fragment view:
final ImageView mImageView = (ImageView) page.findViewById(R.id.imageView2);
And for your 3ยบ question, please explain what you mean by "move by circle", then I'll update my post(if I can) with an answer.
MainActivity.java
// Initialize the ViewPager and set an adapter
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));
ViewPagerAdapter.java
public class ViewPagerAdapter extends FragmentStatePagerAdapter {
private final int PAGES = 3;
private String[] titles={"News", "Organizations", "Map"};
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new TabFragment1();
case 1:
return new TabFragment2();
case 2:
return new TabFragment3();
default:
throw new IllegalArgumentException("The item position should be less or equal to:" + PAGES);
}
}
#Override
public CharSequence getPageTitle(int position) {
return titles[position];
}
#Override
public int getCount() {
return PAGES;
}
}
As you see for each page I have individual fragment documents. Here below one of them:
public class TabFragment1 extends Fragment{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_tab_1, container, false);
}
}
fragment_tab_1.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="#drawable/city"
android:id="#+id/imageView2"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>

Restore views in a fragment added programmatically

I have a single activity with a navigation drawer (the basic one provided by Eclipse new app wizard). I have a FrameLayout as a container for the different fragments of the app, which are replaced when selecting an item in the navigation drawer. They are also added to the BackStack.
These fragments contain a LinearLayout, which has some EditTexts and a Button. If the button is pressed, a new LinearLayout is created and a couple TextViews are added to it with the content of the EditTexts. The user can repeat this option more than once, so I cannot tell how many LinearLayouts I'll need, therefore I need to add them programmatically.
One of these fragments xml:
<LinearLayout
android:id="#+id/pen_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<LinearLayout
android:id="#+id/new_pen_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/activity_vertical_margin"
android:background="#drawable/border"
android:orientation="vertical"
android:paddingBottom="#dimen/home_section_margin_bottom"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/home_section_margin_top" >
<EditText
android:id="#+id/new_pen_round"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:hint="#string/new_pen_round_hint"
android:textSize="#dimen/normal_text_size" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2" >
<Button
android:id="#+id/new_pen_cancel_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="#dimen/new_item_button_margin_right"
android:layout_weight="1"
android:background="#drawable/button_bg"
android:paddingBottom="#dimen/new_item_button_padding_bottom"
android:paddingTop="#dimen/new_item_button_padding_top"
android:text="#string/new_item_cancel_button"
android:textSize="#dimen/normal_text_size" />
<Button
android:id="#+id/new_pen_insert_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/new_item_button_margin_left"
android:layout_weight="1"
android:background="#drawable/button_bg"
android:paddingBottom="#dimen/new_item_button_padding_bottom"
android:paddingTop="#dimen/new_item_button_padding_top"
android:text="#string/new_pen_insert_button"
android:textSize="#dimen/normal_text_size" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
There are actually many other EditTexts but I removed them here to keep it short, the result is the same. It's java file:
public class PenaltiesFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_penalties, container, false);
Button insertNewPen = (Button) view.findViewById(R.id.new_pen_insert_button);
insertNewPen.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
TextView round = (TextView) getActivity().findViewById(R.id.new_pen_round);
LinearLayout layout = (LinearLayout) getActivity().findViewById(R.id.pen_layout);
int numChilds = layout.getChildCount();
CustomPenaltyLayout penalty = new CustomPenaltyLayout(getActivity(), round.getText());
layout.addView(penalty, numChilds - 1);
}
});
return view;
}
}
I removed some useless methods, which are just the default ones. CustomPenaltyLayoutis a subclass of LinearLayout which I created, it just creates some TextViews and adds them to itself.
Everything works fine here. The user inserts data in the EditText, presses the Insert button and a new layout is created and added in the fragment.
What I want to achieve is: say that I open the navigation drawer and select another page, the fragment gets replaced and if I go back to this fragment (via navigation drawer or via Back button) I want the text, that the user added, to be still there.
I do not call PenaltiesFragment.newInstance() everytime I switch back to this fragment, I instead create the PenaltiesFragment object once and keep using that one. This is what I do:
Fragment fragment;
switch (newContent) {
// various cases
case PEN:
if(penFragment == null) // penFragment is a private field of the Main Activity
penFragment = PenaltiesFragment.newInstance();
fragment = penFragment;
break;
}
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, fragment)
.addToBackStack("fragment back")
.commit();
I understand that onCreateView() is called again when the fragment is reloaded, right? So that is probably why a new, blank fragment is what I see. But how do I get the inserted CustomPenaltyLayout back? I cannot create it in the onCreateView() method.
I found a solution to my problem. I replaced the default FrameLayout that Android automatically created as a container for my fragments, with a ViewPager, then created a FragmentPagerAdapter like this:
public static class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment;
switch (position) {
// ...other cases
case PEN:
fragment = PenaltiesFragment.newInstance();
break;
// ...other cases
}
return fragment;
}
#Override
public int getCount() {
return 6;
}
}
Then the only thing left to do to keep all the views at all times has been to add this line to my activity onCreate method.
mPager.setOffscreenPageLimit(5);
See the documentation for details on how this method works.
This way, though, I had to reimplement all the back button logic, but it's still simple, and this is how I did it: I create a java.util.Stack<Integer> object, add fragment numbers to it (except when you use the back button, see below), and override onBackPressed() to make it pop the last viewed fragment instead of using the back stack, when my history stack is not empty.
You want to avoid pushing elements on the Stack when you press the back button, otherwise you will get stuck between two fragments if you keep using the back button, instead of eventually exiting.
My code:
MyAdapter mAdapter;
ViewPager mPager;
Stack<Integer> pageHistory;
int currentPage;
boolean saveToHistory;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAdapter = new MyAdapter(getSupportFragmentManager());
mPager = (ViewPager)findViewById(R.id.container);
mPager.setAdapter(mAdapter);
mPager.setOffscreenPageLimit(5);
pageHistory = new Stack<Integer>();
mPager.setOnPageChangeListener(new OnPageChangeListener() {
#Override
public void onPageSelected(int arg0) {
if(saveToHistory)
pageHistory.push(Integer.valueOf(currentPage));
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
saveToHistory = true;
}
#Override
public void onBackPressed() {
if(pageHistory.empty())
super.onBackPressed();
else {
saveToHistory = false;
mPager.setCurrentItem(pageHistory.pop().intValue());
saveToHistory = true;
}
};

Do I need fragments and how to access them?

I am new to Android so my question may seem ridiculous but I cant figure it out.
I started creating an app some time ago and using 'Create new Android Activity' usually created a .java and .xml file for it, and everything worked. Now, after update when I use 'Create new Android Activity' it creates .java with class (which now extends ActionBarActivity and not Activity as before) and it adds a fragment_nameofactivity.xml + all things to make it work like internal class extending Fragment...
Now I used to do some ListView display on the page and without a fragment it all works great, but when fragment got introduced I can no longer findViewById(R.id.list_view) if its inside a fragment...
My question is do I need to place my whole functionality inside the class extending Fragment? I tried but it didn't work... Or do I still write all my functionality in the original class and then somehow access the listView in the fragment...
Here is the code:
public class PlayersActivity extends ActionBarActivity {
PlayerDataDatabaseAdapter playerDataHelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_players);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
}
playerDataHelper = new PlayerDataDatabaseAdapter(this);
playerDataHelper.open();
displayPlayersList();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.players, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
public PlaceholderFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_players,
container, false);
return rootView;
}
}
private void displayPlayersList() {
Cursor cursor = playerDataHelper.getAllPlayers();
String [] columns = playerDataHelper.columnsToBind();
int [] to = new int[] {
R.id.player_name,
};
SimpleCursorAdapter dataAdapter = new SimpleCursorAdapter(this, R.layout.fragment_player_details, cursor, columns, to, 0);
ListView listView = (ListView) findViewById(R.id.players_list);
listView.setAdapter(dataAdapter);
listView.setOnItemClickListener(new OnItemClickListener(){
#Override
public void onItemClick(AdapterView<?> listView, View view, int position, long id) {
Cursor cursor = (Cursor) listView.getItemAtPosition(position);
int player_id = cursor.getInt(cursor.getColumnIndexOrThrow("_id"));
Intent intent = new Intent(PlayersActivity.this, EditPlayerActivity.class);
intent.putExtra("PlayerId", player_id);
startActivity(intent);
}
});
}
public void addNewPlayer(View view) {
Intent intent = new Intent(this, AddPlayerActivity.class);
startActivity(intent);
}
}
Fragment_players.xml
<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"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="uk.co.eximage.soccermum.PlayersActivity$PlaceholderFragment" >
<TextView
android:id="#+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_alignParentTop="true"
android:text="#string/players"
android:textAppearance="?android:attr/textAppearanceLarge" />
<Button
android:id="#+id/button1"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal = "true"
android:layout_below="#+id/textView1"
android:onClick="addNewPlayer"
android:text="#string/add_player" />
<ListView
android:id="#+id/players_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/button1"
>
</ListView>
</RelativeLayout>
activity_players.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="uk.co.eximage.soccermum.PlayersActivity"
tools:ignore="MergeRootFrame" />
Running this returns NullPointerException on the line that tries to get players_list:
ListView listView = (ListView) findViewById(R.id.players_list);
after this listView is null.
What am I doing wrong?
And finally do I need fragments? Maybe I should just remove them and do it the 'old' way with one view per page?
You need to iniaitlize ListView in Fragment
ListView listView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_players,
container, false);
listView = (ListView)rootView. findViewById(R.id.players_list);
playerDataHelper = new PlayerDataDatabaseAdapter(getActivity());
playerDataHelper.open();
displayPlayersList();
The ListView belongs to fragment_players.xml. Move all your code related to fragment in onCreateView.
Edit:
public static class PlaceholderFragment extends Fragment {
public PlaceholderFragment() {
}
ListView listView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_players,
container, false);
listView = (ListView)rootView. findViewById(R.id.players_list);
playerDataHelper = new PlayerDataDatabaseAdapter(getActivity());
playerDataHelper.open();
displayPlayersList();
return rootView;
}
private void displayPlayersList() {
Cursor cursor = playerDataHelper.getAllPlayers();
String [] columns = playerDataHelper.columnsToBind();
int [] to = new int[] {
R.id.player_name,
};
SimpleCursorAdapter dataAdapter = new SimpleCursorAdapter(getActivity(), R.layout.fragment_player_details, cursor, columns, to, 0);
listView.setAdapter(dataAdapter);
listView.setOnItemClickListener(new OnItemClickListener(){
#Override
public void onItemClick(AdapterView<?> listView, View view, int position, long id) {
Cursor cursor = (Cursor) listView.getItemAtPosition(position);
int player_id = cursor.getInt(cursor.getColumnIndexOrThrow("_id"));
Intent intent = new Intent(getActivity(), EditPlayerActivity.class);
intent.putExtra("PlayerId", player_id);
startActivity(intent);
}
});
}
}
Fragments were introduced to better support the tablet form factor. If you don't plan to rearrange your display (ie. show list and detail view together), you don't need fragments and can go the old way.
You should have to initialize Listview from fragment rootView
Either you have to Declare ListView globally and intialize inside onCreateView of Fragment or have to declare View rootView globally and initialize listview by
ListView listView = (ListView) rootView .findViewById(R.id.players_list);

OnClickListener in ArrayAdapter is taking action on wrong rows

I have an ArrayAdapter for a list view that has multiple buttons in it. For one toggle button, I want to have a default state based on a condition, and let users toggle the button as well.
However, when users click button on row 1, the button for row 3 actually gets selected. I'm not sure why this is happening. Below is snippet of relevant code from my getView method with comments.
layout of my toggle button
<ToggleButton android:id="#+id/color_toggle"
android:layout_width="50px"
android:layout_height="50px"
android:focusable="false"
android:textOn="" android:textOff="" android:layout_alignParentLeft="true"
android:layout_marginRight="10dp"
/>
class Color {
int id;
int something;
}
List<Color> colorsList;
class ColorHolder {
TextView colorNameText;
ToggleButton toggleButton;
}
public View getView(final int position, final View convertView, final ViewGroup parent) {
View rowView = convertView;
Color c = colorsList.get(position);
if (null == rowView) {
rowView = this.inflater.inflate(R.layout.list_item_color, parent, false);
holder = new ColorHolder();
holder.colorNameText = (TextView) rowView.findViewById(R.id.color_name);
holder.toggleButton = (ToggleButton) rowView.findViewById(R.id.color_toggle);
rowView.setTag(holder);
}
else {
holder = (ColorHolder)rowView.getTag();
}
holder.toggleButton.setTag(c.getId());
final ColorHolder thisRowHolder = holder;
holder.toggleButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (thisRowHolder.toggleButton.isChecked()) {
thisRowHolder.toggleButton.setBackgroundDrawable(//normal button);
thisRowHolder.toggleButton.setChecked(false);
for (int i = 0; i < colorList.size(); i++) {
if (colorList.get(i) == (Integer)v.getTag()) {
colorList.get(i).setSomething(0);
break;
}
}
adapter.notifyDataSetChanged();
}
else {
thisRowHolder.toggleButton.setBackgroundDrawable(//enabled button);
thisRowHolder.toggleButton.setChecked(true);
for (int i = 0; i < colorList.size(); i++) {
if (colorList.get(i) == (Integer)v.getTag()) {
colorList.get(i).setSomething(1);
break;
}
}
adapter.notifyDataSetChanged();
}
}
});
if (c.getSomething()>0) {
holder.toggleButton.setBackgroundDrawable(//enabled button);
holder.toggleButton.setChecked(true);
}
else {
holder.toggleButton.setBackgroundDrawable(//normal button);
holder.toggleButton.setChecked(false);
}
return rowView;
}
Question
What am I doing wrong? why are other buttons in third row toggling even though i'm toggling buttons in row one.
I read that this happens because the listView recycles, is there no way to fix it? Some strategies i've tried, to no avail, based on similar questions: 1) put onClickListener in the if clause. 2) instead of setting int in setTag instead set the holder and use that holder in onClickListener
update
I've updated all the code in the question with suggestions I received.
Hope This Helps.
Activity Code
public class DemoActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ColorInfo[] clr= new ColorInfo[20];
for(int i=0;i<20;i++){
clr[i] = new ColorInfo();
}
((ListView)findViewById(R.id.list)).setAdapter(new MyAdapter(this, 0, clr));
}
private static class MyAdapter extends ArrayAdapter<ColorInfo> implements OnClickListener{
LayoutInflater inflater;
public MyAdapter(Context context, int textViewResourceId,
ColorInfo[] objects) {
super(context, textViewResourceId, objects);
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null){
convertView = inflater.inflate(R.layout.row, null);
holder = new ViewHolder();
holder.tgl = (ToggleButton) convertView.findViewById(R.id.toggle);
convertView.setTag(holder);
}
holder = (ViewHolder) convertView.getTag();
holder.tgl.setTag(position);
holder.tgl.setOnClickListener(this);
holder.tgl.setChecked(getItem(position).isChecked);
return convertView;
}
private static class ViewHolder{
ToggleButton tgl;
}
public void onClick(View v) {
int pos = (Integer) v.getTag();
ColorInfo cinfo = getItem(pos);
cinfo.isChecked = !cinfo.isChecked;
}
}
private static class ColorInfo{
boolean isChecked=false;
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ListView
android:id="#+id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
</ListView>
</LinearLayout>
row.xml
<?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="match_parent"
android:orientation="vertical" >
<ToggleButton android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/toggle"
/>
</LinearLayout>
Your problem is listview recycling views
You have to store state of toggle button for each row of listview.
Eg.Create class which stores information about each row,suppose ColorInfo which contains color and isChecked boolean.
so instead of
Color c = colorsList.get(position);
it will be
ColorInfo colorInfo = colorsList.get(position);
and in getview
togglebutton.setCheck(colorInfo.isCheck)
and in onClick listener of toggle buttons you change state of object of ColorInfo for that position to toggleChecked true or false and notifyDatasetChanged,this will solve your problem.
You are using a member variable for your ViewHolder, rather than a final local variable. So your OnClickListener is referencing whatever the latest holder instance is, which will correspond with the most recently created or recycled list item.
Do this instead:
//Lock in this reference for the OnClickListener
final ColorHolder thisRowHolder = holder;
holder.favButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (thisRowHolder.toggleButton.isChecked()) {
thisRowHolder.toggleButton.setBackgroundDrawable(getResources().getDrawable(...);
thisRowHolder.toggleButton.setChecked(false);
}
else {
thisRowHolder.toggleButton.setBackgroundDrawable(getResources().getDrawable(...));
thisRowHolder.toggleButton.setChecked(true);
}
}
});
...
Edit:
Also noticed this. In these two lines:
holder.colorNameText = (TextView) itemView.findViewById(R.id.color_name);
holder.toggleButton = (ToggleButton) itemView.findViewById(R.id.color_toggle);
You are finding the views in some member variable itemView, but you need to be finding them in rowView so you are getting the instances for this specific row. All your view holders are looking at the same ToggleButton instance, which may not even be on screen.
Edit 2:
One more thing you're missing. You need to store the state of the toggle buttons and reapply them. So in your OnClickListener, when you call setChecked() you must also update the backing data in colorsList. Looks like you already cached a reference to the proper list element in your ToggleButton's ID, so should be easy. Then move this block of code out of your if/else block and put it afterwards, so the toggle button is always updated to the latest data:
if (c.getSomething()>0) {
holder.toggleButton.setBackgroundDrawable(getResource().getDrawable(...)));
holder.setChecked(false);
}
else {
holder.toggleButton.setBackgroundDrawable(getResource().getDrawable(...)));
holder.setChecked(true);
}

Android ListView lazy loading stuff behaves unexpectedly

I'm developing an Android app in which I've got some lists. It's more complex than I'll explain here, but the scenario I'm posting behaves unexpectedly as well.
I've been browsing several questions in Stack Overflow but none that I found solves this issue.
So, for the sake of the question I've built this scenario. I've got an Activity with an array of Strings, like so:
public class TestViewlazyloadActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
String[] items = { "lorem", "ipsum", "dolor", "sit", "amet" };
ListView list = (ListView) findViewById(R.id.testListView);
TestAdapter adapter = new TestAdapter(this, R.layout.list_item, items);
list.setAdapter(adapter);
}
}
Being my two layouts this way:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ListView
android:id="#+id/testListView"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
</ListView>
</LinearLayout>
And this way:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:id="#+id/testTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge" />
</LinearLayout>
Pretty simple. Then I've implemented an ArrayAdapter class with the ViewHolder idea, which getView() method is as follows:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
String item = this.getItem(position);
if(convertView == null) {
LayoutInflater inflater = ((Activity) this.getContext()).getLayoutInflater();
convertView = inflater.inflate(R.layout.list_item, null);
holder = new ViewHolder();
holder.textView = (TextView) convertView.findViewById(R.id.testTextView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
final ViewHolder auxHolder = holder;
LoadingService.capitalizeItemWithDelay(item, new LoadedCallback() {
public void onItemLoaded(String item) {
auxHolder.textView.setText(item);
}
});
return convertView;
}
Being LoadedCallback an interface with just onItemLoaded. And LoadingService.capitalizeItemWithDelay() just starts a Thread which sleeps for a random amount of time (to simulate a lazy load) and calls this callback with the upper case String.
Normal behavior should be that user is viewing the list with 5 blank rows. After some time items begin to spawn in their respective rows, even in a random order given the random sleep timer.
What really happens is that items get loaded in the first row, one by one, and eventually some of them get set in their real place. It's like the auxHolder keeps the reference in its TextView to the first element, which is weird. I understand it's either because of the view being recycled or because of the auxHolder being final.
Any ideas? Thanks in advance.
Get rid of final on your ViewHolder variable and move ViewHolder auxHolder into the inner class.
Try this:
...
LoadingService.capitalizeItemWithDelay(item, new LoadedCallback() {
ViewHolder auxHolder = holder;
public void onItemLoaded(String item) {
auxHolder.textView.setText(item);
When you use the final key word with a local variable, like auxHolder, you are saying that the value you give it, is the last value it will have. This is why you are having problems. Here is a link that will explain a little better: http://www.javapractices.com/topic/TopicAction.do?Id=23
You may not be updating on the UI Thread.
I would try performing your loading actions in a post and see that fixes everything.
Try doing something like this
convertView.post(new Runnable() {
#Override
public void run() {
LoadingService.capitalizeItemWithDelay(item, new LoadedCallback() {
ViewHolder auxHolder = holder;
public void onItemLoaded(String item) {
auxHolder.textView.setText(item);
}
}
}
});

Categories