问题
Disclosure up front, this is a school project.
I've been working on an activity in a project that consists of a series of dynamic action bar tabs and list fragments, and have been stuck on the prospect of getting object references to the specific tab fragments in order to set adapters for the list views.
I have it set up at the moment using a ViewPager (the support v4 version) and a FragmentStatePagerAdapter (v13). I've tried methods mentioned in a few SO question threads, namely these three:
Updating fragments/views in viewpager with fragmentStatePagerAdapter
Retrieve a Fragment from a ViewPager
Android ViewPager - can't update dynamically
However, each of these attempts have been to no avail, here is the relevant code from my attempt to try and demonstrate what I'm trying to accomplish.
The code for the FragmentStatePagerAdapter:
/**
* The Class ServerPagerAdapter.
*/
private class ServerPagerAdapter extends FragmentStatePagerAdapter
{
SparseArray<ServerViewFragment> registeredFragments = new SparseArray<ServerViewFragment>();
/**
* Instantiates a new server pager adapter.
*
* @param fm the fragment manager
*/
public ServerPagerAdapter(FragmentManager fm)
{
super(fm);
}
/* (non-Javadoc)
* @see android.support.v13.app.FragmentPagerAdapter#getItem(int)
*/
@Override
public Fragment getItem(int position)
{
ServerViewFragment serverFrag = new ServerViewFragment();
registeredFragments.put(position, serverFrag);
return serverFrag;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object)
{
super.destroyItem(container, position, object);
registeredFragments.remove(position);
}
/* (non-Javadoc)
* @see android.support.v4.view.PagerAdapter#getCount()
*/
@Override
public int getCount()
{
// TODO STATIC VALUE.
return 3;
}
/* (non-Javadoc)
* @see android.support.v4.view.PagerAdapter#getPageTitle(int)
*/
@Override
public CharSequence getPageTitle(int position)
{
//TODO: HARD CODED
switch (position)
{
case 0:
return "Status";
case 1:
return "#chat";
case 2:
return "#help";
}
return null;
}
public ServerViewFragment getFragment(int position)
{
return registeredFragments.get(position);
}
}
The code in the activity that I'm trying to get to work:
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_server_view);
/*Code omitted (actionbar navigation)*/
//connect pager
servPagerAdapter = new ServerPagerAdapter(getFragmentManager());
viewPager = (ViewPager)findViewById(R.id.server_view_pager);
viewPager.setAdapter(servPagerAdapter);
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener()
{
@Override
public void onPageSelected(int position)
{
actionBar.setSelectedNavigationItem(position);
}
});
//populate tabs
for (int i = 0; i < servPagerAdapter.getCount(); i++)
{
Tab tabToAdd = actionBar.newTab();
tabToAdd.setText(servPagerAdapter.getPageTitle(i));
tabToAdd.setTabListener(this);
actionBar.addTab(tabToAdd);
}
svf = servPagerAdapter.getFragment(0);
csvf = servPagerAdapter.getFragment(1);
IRCConnection testConn = new IRCConnection();
testConn.setOnIRCMessageReceivedListener(this);
//adapters are populated in a separate method
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
cadapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
Log.i("ADAPTER", adapter == null ? "Null" : "Not Null");
Log.i("FRAGMENT", svf == null ? "Null" : "Not Null");
//svf.setListAdapter(adapter);
//csvf.setListAdapter(cadapter);
/*code omitted (network call)*/
}
svf and csvf are variables I'm using to just try and test getting it to work, they are typed of a ListFragment based class that is completely cookie cutter. Eventually I want to be able to add and remove tabs from the adapter and manage the listviews in each of them.
At this stage it's probably a just an issue of me not understanding the topic/way the adapter works, and I'm hoping this is just an ignorance based question, and that there's a simple solution.
Edit: I've made some progress, but calling refresh on the visible tab is throwing a null pointer for some reason. Here's the callback from my connection class, the callback being inside the activity class:
@Override
public void onNetworkMessageReceived(String message)
{
final String msg = message;
this.runOnUiThread(new Runnable()
{
@Override
public void run()
{
FragmentData data = fragmentDataMapper.getData("Network");
ServerViewFragment visFrag = servPagerAdapter.getFragment(getActionBar().getSelectedNavigationIndex());
if (data == null)
{
ArrayList<String> chatStrings = new ArrayList<String>();
chatStrings.add(msg);
data = new FragmentData(chatStrings);
visFrag.refresh(data);
fragmentDataMapper.updateData("Network", data);
}
else
{
ArrayList<String> chatStrings = data.getChatStrings();
chatStrings.add(msg);
data = new FragmentData(chatStrings);
visFrag.refresh(data);
fragmentDataMapper.updateData("Network", data);
}
}
});
Log.i("HANDLER", message);
}
The exception is being thrown on either of the visFrag.refresh lines, as visFrag is null. I'm still using the same registeredFragments thing from before.
回答1:
Given the latest comment, I have a suggestion of how you perhaps could get the behaviour you seek.
I believe that your setup with the activity, the tabs and the fragments makes perfect sense. It is only the communication between activity and fragment which needs to be addressed.
In my own project I make use of the registeredFragments approach but only for the currently visible fragment, as that one will always be non-null. This is neat to update the currently visible information but I think that the update has to be communicated to the other fragments differently in order to be stable.
I will not address how the update is done in your setting specifically, as I do not know how and when you currently handle this, so this will be a bit general.
Ok, so here is my suggestion:
Simply have a static class which holds a mapping between fragments and the information you want to show. This mapping could be between a unique name or a pagenumber given to each fragment to a model of the information it should display. The idea is outlined here here:
public class FragmentDataMapping {
// FragmentId could be anything like String or Integer
private Map<FragmentId, FragmentData> fragmentData = new HashMap<>();
public static void updateData(FragmentId fragmentId, FragmentData fragmentData) {
fragmentData.put(fragmentId, fragmentData);
}
public static FragmentData getData(FragmentId fragmentId) {
return fragmentData.get(fragmentId);
}
}
// FragmentData could be any kind of model object for your fragments
public class FragmentData {
private String someData;
private Integer someOtherData;
private... and so on
// constructor, setter and getter methods
}
Using some sort of Mapping and Model data like this, you can now in your activity, as soon as you get data from the server, create a FragmentData object with the information and associate it with a fragment.
public void serverResponse(String someData, Integer someOtherData) {
FragmentData newData = new FragmentData(someData, someOtherData);
FragmentDataMapping.updateData("DetailsFragment", newData);
}
And in onResume of your fragments, you would inspect the FragmentData to get the most recent data.
// This is inside DetailsFragment
private String fragmentId = "DetailsFragment"; // <- some id assigned at creation
public void onResume() {
FragmentData data = FragmentDataMapping.getData("DetailsFragment");
Log.d("DetailsFragment", "My data: " + data.getSomeData() + " and " + data.getSomeOtherData());
refresh(fragmentData)
....
}
public void refresh(FragmentData fragmentData) {
// set adapters, update view or do whatever with the data.
// the reason for creating a seperate method to handle the fragmentData follows below
}
With this (or something like this), the fragments will update the data as soon as a new tab gets selected.
Now, the currently visible fragment would not update itself with this method as onResume() is not called. Therefore, to update the current fragment you can use the registeredFragments of your viewpager:
public void serverResponse(String someData, Integer someOtherData) {
FragmentData newData = new FragmentData(someData, someOtherData);
FragmentDataMapping.updateData("DetailsFragment", newData);
registeredFragments(getActionBar().getSelectedNavigationIndex()).refresh(newData);
}
I know this description of an implementation is vague but I hope it has some ideas and some points that you can benefit from investigating.
来源:https://stackoverflow.com/questions/20660205/android-get-fragment-reference-from-viewpager-fragmentstatepageradapter