Does somebody know of a tutorial or an example of how to implement the standard Android search interface with Fragments? In other words, is it possible to put a standard search with a SearchManager in a Fragment?
In short, you can't. There are a couple of reasons why creating a search interface within a Fragment is not possible.
When creating a searchable interface, you must specify a default "searchable activity" in your Android manifest. As I'm sure you know, a Fragment cannot exist without a parent Activity and thus, this separation is not possible.
If you already figured out #1 already, I assume you asked this question in hopes that there is some magical "hack" out there that can get the job done. However, the documentation states that,
When the user executes a search in the search dialog or widget, the
system starts your searchable activity and delivers it the search
query in an Intent with the ACTION_SEARCH action. Your searchable
activity retrieves the query from the intent's QUERY extra, then
searches your data and presents the results.
The underlying, internal system that is responsible for providing search results expects an Activity, not a Fragment; thus, implementing a search interface that is completely independent of an Activity is not possible, as it would require changes to the underlying system itself. Check out the source code for the SearchableInfo class if you don't believe me :).
That being said, it doesn't seem like it would be too difficult to achieve something similar to what you are describing. For instance, you might consider implementing your searchable-Activity so that it will accept the android.intent.action.SEARCH intent and (instead of immediately displaying the results in a ListView, for example) will pass the search query to your Fragments. For instance, consider the following searchable Activity:
public class SearchableActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Intent.ACTION_SEARCH.equals(getIntent().getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
doMySearch(query);
}
}
/**
* Performs a search and passes the results to the container
* Activity that holds your Fragments.
*/
public void doMySearch(String query) {
// TODO: implement this
}
}
When a search-request is made, the system will launch your searchable activity, perform the query, and will pass the results to some container Activity (based on your implementation of doMySearch). The container Activity will then pass these results to the contained searchable Fragment, in which the results will be displayed. The implementation requires a bit more work than what you were probably hoping for, but I'm sure there are ways that you can make it more modular, and it seems like this might be the best that you can do.
p.s. If you use this approach, you might have to pay special attention to which Activitys are added/removed to the backstack. See this post for some more information on how this might be done.
p.p.s. You might also forget about the standard search interface completely and just implement a simple search within a Fragment as described in Raghav's post below.
Here is the example to search something using fragments. Hope it helps and this is what you are looking for:
public class LoaderCursor extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentManager fm = getFragmentManager();
// Create the list fragment and add it as our sole content.
if (fm.findFragmentById(android.R.id.content) == null) {
CursorLoaderListFragment list = new CursorLoaderListFragment();
fm.beginTransaction().add(android.R.id.content, list).commit();
}
}
public static class CursorLoaderListFragment extends ListFragment
implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
// If non-null, this is the current filter the user has provided.
String mCurFilter;
#Override public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText("No phone numbers");
// We have a menu item to show in action bar.
setHasOptionsMenu(true);
// Create an empty adapter we will use to display the loaded data.
mAdapter = new SimpleCursorAdapter(getActivity(),
android.R.layout.simple_list_item_2, null,
new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
new int[] { android.R.id.text1, android.R.id.text2 }, 0);
setListAdapter(mAdapter);
// Start out with a progress indicator.
setListShown(false);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
#Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Place an action bar item for searching.
MenuItem item = menu.add("Search");
item.setIcon(android.R.drawable.ic_menu_search);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
SearchView sv = new SearchView(getActivity());
sv.setOnQueryTextListener(this);
item.setActionView(sv);
}
public boolean onQueryTextChange(String newText) {
// Called when the action bar search text has changed. Update
// the search filter, and restart the loader to do a new query
// with this filter.
mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
getLoaderManager().restartLoader(0, null, this);
return true;
}
#Override public boolean onQueryTextSubmit(String query) {
// Don't care about this.
return true;
}
#Override public void onListItemClick(ListView l, View v, int position, long id) {
// Insert desired behavior here.
Log.i("FragmentComplexList", "Item clicked: " + id);
}
// These are the Contacts rows that we will retrieve.
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
Contacts._ID,
Contacts.DISPLAY_NAME,
Contacts.CONTACT_STATUS,
Contacts.CONTACT_PRESENCE,
Contacts.PHOTO_ID,
Contacts.LOOKUP_KEY,
};
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// First, pick the base URI to use depending on whether we are
// currently filtering.
Uri baseUri;
if (mCurFilter != null) {
baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
Uri.encode(mCurFilter));
} else {
baseUri = Contacts.CONTENT_URI;
}
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+ Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+ Contacts.DISPLAY_NAME + " != '' ))";
return new CursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION, select, null,
Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
// The list should now be shown.
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
}
}
It is quite possible to search in a fragment using the standard ActionBar SearchView ActionView API. This will work back to Android 2.1 (API level 7) too using AppCompat support classes v7.
In your fragment:
#Override
public void onCreateOptionsMenu (Menu menu, MenuInflater inflater){
inflater.inflate(R.menu.search, menu);
MenuItem item = menu.findItem(R.id.action_search);
SearchView sv = new SearchView(((YourActivity) getActivity()).getSupportActionBar().getThemedContext());
MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW | MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
MenuItemCompat.setActionView(item, sv);
sv.setOnQueryTextListener(new OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
System.out.println("search query submit");
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
System.out.println("tap");
return false;
}
});
}
In your menu XML
<item
android:id="#+id/action_search"
android:icon="#drawable/ic_action_search"
android:title="Search Waste Items"
android:showAsAction="ifRoom|collapseActionView"
nz.govt.app:actionViewClass="android.support.v7.widget.SearchView"
nz.govt.app:showAsAction="ifRoom|collapseActionView" />
Using AppCompat support classes v7. Just adding something to #David 's solution from #Rookie solution to get it work properly in a simple manner, here is my fragment code:
MyFragment:
public class MyFragment extends Fragment implements SearchView.OnQueryTextListener {
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// What i have added is this
setHasOptionsMenu(true);
}
#Override
public void onCreateOptionsMenu (Menu menu, MenuInflater inflater) {
//inflater.inflate(R.menu.main, menu); // removed to not double the menu items
MenuItem item = menu.findItem(R.id.action_search);
SearchView sv = new SearchView(((MainActivity) getActivity()).getSupportActionBar().getThemedContext());
MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW | MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
MenuItemCompat.setActionView(item, sv);
sv.setOnQueryTextListener(this);
sv.setIconifiedByDefault(false);
sv.setOnSearchClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Utils.LogDebug("Clicked: ");
}
});
MenuItemCompat.setOnActionExpandListener(item, new MenuItemCompat.OnActionExpandListener() {
#Override
public boolean onMenuItemActionCollapse(MenuItem item) {
// Do something when collapsed
Utils.LogDebug("Closed: ");
return true; // Return true to collapse action view
}
#Override
public boolean onMenuItemActionExpand(MenuItem item) {
// Do something when expanded
Utils.LogDebug("Openeed: ");
return true; // Return true to expand action view
}
});
super.onCreateOptionsMenu(menu,inflater);
}
#Override
public boolean onQueryTextSubmit(String query) {
Utils.LogDebug("Submitted: "+query);
return true;
}
#Override
public boolean onQueryTextChange(String newText) {
Utils.LogDebug("Changed: "+newText);
return false;
}
}
I added the onActivityCreated, cuz without calling setHasOptionsMenu(true); the system will not know that this fragment needs to interact with the menu.
then I removed the line inflater.inflate(R.menu.main, menu); because it doubled the menu items since Activity inflated a menu, then Fragment inflated another menu
Thanks to #David and #Rookie
When working with Fragments you still need to use an Activity to control and assign the Fragments.
This Activity can have search functionality as before.
I've recently switched from a 'normal' Activity based app, to a Fragment based app and the search functionality worked just the same for me.
Have you tried working on it, and didn't succeed? If so give some more detail in your question.
EDIT:
If you want to have a fragment specific search, have all your Fragments extend an interface MyFragment with a startSearch method, and have your Activity's startSearch method call the current fragment's startSearch method.
I think I achieved it : you can actually use fragments and add a search icon to an action bar so that a search is possible inside the fragments. The trick is to use an action bar, an action view, a listener for it, a loader and an adapter of course.
This works pretty well although it completely bypasses the android platform search mechanism (but it could be completed with some work to find what #Alex Lockwood describes and pass the search to fragments). It would not react to an intent as expected in the case of an activity, but it works : users can search inside fragments.
Here is the code :
SearchInFragmentActivity
package com.sof.test.searchfragment;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.view.View;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.app.ActionBar.Tab;
import com.actionbarsherlock.app.ActionBar.TabListener;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.sof.test.searchfragment.SearchFragment;
import com.sof.test.R;
public class SearchInFragmentActivity extends SherlockFragmentActivity implements TabListener {
private SearchFragment tab1 = new SearchFragment();
private SearchFragment tab2 = new SearchFragment();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView( R.layout.search_in_fragments );
getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
createTab( R.string.tab1, R.drawable.ic_menu_search );
createTab( R.string.tab2, R.drawable.ic_menu_search );
getSupportActionBar().setSelectedNavigationItem( 0 );
invalidateOptionsMenu();
}
private void createTab(int tabNameResId, int tabIconResId) {
ActionBar.Tab tab = getSupportActionBar().newTab();
tab.setText( tabNameResId );
tab.setTabListener(this);
getSupportActionBar().addTab(tab);
}// met
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if( ft == null ) {
return;
}//if
View fragmentSlot = findViewById( R.id.fragment );
Fragment newFragment = null;
if( fragmentSlot != null ) {
newFragment = (tab.getPosition() == 0) ? tab1 : tab2;
ft.replace(R.id.fragment, newFragment );
ft.setTransition( FragmentTransaction.TRANSIT_FRAGMENT_FADE);
}//if
}
#Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
#Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
}//class
The fragment class SearchFragment (I use 2 instances inside the activity above).
package com.sof.test.searchfragment;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.AsyncTaskLoader;
import android.support.v4.content.Loader;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.SearchView;
import android.widget.TextView;
import com.sof.test.R;
import com.actionbarsherlock.app.SherlockListFragment;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
public class SearchFragment extends SherlockListFragment {
private StringLoader loader = null;
private StringAdapter adapter = null;
private List<String> listData = new ArrayList<String>();
private String query;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = super.onCreateView(inflater, container, savedInstanceState);
createListData();
loader = new StringLoader( getActivity(), this );
adapter = new StringAdapter(listData);
setListAdapter(adapter);
getLoaderManager().initLoader(0, null, new LoaderCallBacks() );
loader.forceLoad();
setHasOptionsMenu( true );
return view;
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater ) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate( R.menu.menu_search, menu);
System.out.println( "inflating menu");
final SearchView searchView = (SearchView) menu.findItem(R.id.menu_search).getActionView();
final SearchView.OnQueryTextListener queryTextListener = new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextChange(String newText) {
showFilteredItems( newText );
return true;
}
#Override
public boolean onQueryTextSubmit(String query) {
return true;
}
};
searchView.setOnQueryTextListener(queryTextListener);
return;
}//met
private void showFilteredItems( String query ) {
this.query = query;
loader.onContentChanged();
}
private void createListData() {
for( int i = 0; i < 100 ; i ++ ) {
listData.add( "String "+ i );
}
}
public List<String> getData() {
List<String> listFilteredData = new ArrayList<String>();
for( String string : listData ) {
if( query == null || string.contains( query ) ) {
listFilteredData.add( string );
}
}
return listFilteredData;
}//met
private class LoaderCallBacks implements LoaderCallbacks< List<String>> {
#Override
public void onLoadFinished(Loader<List<String>> loader,
List<String> listData) {
adapter.setListData( listData );
}// met
#Override
public void onLoaderReset(Loader<List<String>> listData) {
adapter.setListData( new ArrayList<String>() );
}// met
#Override
public Loader<List<String>> onCreateLoader(int arg0,
Bundle arg1) {
return loader;
}// met
}//class
private class StringAdapter extends ArrayAdapter< String > {
private List<String> listDataToDisplay = new ArrayList<String>();
private LayoutInflater mInflater;
public StringAdapter( List<String> listData ) {
super( getActivity(), android.R.layout.simple_list_item_1, android.R.id.text1, listData );
listDataToDisplay = listData;
mInflater = (LayoutInflater)getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}//cons
private void setListData( List<String> newListData ) {
this.listDataToDisplay.clear();
this.listDataToDisplay.addAll( newListData );
notifyDataSetChanged();
}//met
/**
* Populate new items in the list.
*/
#Override public View getView(int position, View convertView, ViewGroup parent) {
View view;
if (convertView == null) {
view = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false);
} else {
view = convertView;
}
((TextView)view.findViewById( android.R.id.text1)).setText( listDataToDisplay.get( position ) );
return view;
}
}//inner class
}//class
class StringLoader extends AsyncTaskLoader<List<String>> {
SearchFragment fragment = null;
public StringLoader(Context context, SearchFragment fragment) {
super(context);
this.fragment = fragment;
}// cons
#Override
public List<String> loadInBackground() {
return fragment.getData();
}// met
}// class
The xml file for the menu of the search fragments res/menu/menu_search.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:orientation="horizontal" >
<FrameLayout
android:id="#+id/fragment"
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
And the xml layout file res/layout/search_in_fragments.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:orientation="horizontal" >
<FrameLayout
android:id="#+id/fragment"
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
Use the ActionBar and SearchView. You will be able to handle searches without any connection to Activity. Just set an OnQueryTextListener to the SearchView.
MenuItem item = menu.add("Search");
SearchView sv = new SearchView(getActionBar().getThemedContext());
item.setActionView(sv);
item.setIcon(R.drawable.ic_search);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
| MenuItem.SHOW_AS_ACTION_IF_ROOM);
sv.setOnQueryTextListener(new OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
//...
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
//...
return false;
}
});
See this post for more details on custom search.
others solution..... i dont like that.
This more easy for me.But its my idea.Im waiting yours opinion
public interface SearchImpl {
public void searchQuery(String val);
}
Fragment
public class MyFragment extends Fragment implements SearchImpl {
View view;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_almanca, container, false);
return view;
}
#Override
public void searchQuery(String val) {
Log.e("getted", val);
}
}
Acitivty
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.action_search));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
Log.e("setted", "" + query);
try {
MyFragment myFGM=new MyFragment();
myFGM.searchQuery(query);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
return false;
}
});
return super.onCreateOptionsMenu(menu);
}
A Fragment cannot exist outside an Activity, nor can a Fragment be linked to a android.intent.action.SEARCH or any other intent-filter.
So without using an Activity to wrap the the Fragment, what you're asking is not possible.
In case of Fragments into a ViewPager, I could do it by blocking the search button when I'm not on the fragment where I want to give a search bar. Into the activity:
#Override
public boolean onSearchRequested() {
if (mPager.getCurrentItem() == mAdapter.getPosition(FragmentType.VIDEO))
return super.onSearchRequested();
else
return false;
}
And in case of no physical search button, I added an action item into the fragment, which trigger this code:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.search_item) {
return getSherlockActivity().onSearchRequested();
}
return super.onOptionsItemSelected(item);
}
i found a work around :)
you can override this method (startActivity(Intent) ) in your BaseActivity and then check if the action is ACTION_SEARCH then do your special job :D
#Override
public void startActivity(Intent intent) {
try {
if (intent.getAction().equals(Intent.ACTION_SEARCH))
toast("hello");
} catch (Exception e) {
}
}
yes its possible,
please implements Search view on your Activity ,'onQueryTextChange' in Activity will also listen the search in fragment, you can check the fragment visibility in 'onQueryTextChange' , if it visible you can call your search method for fragment, its working perfect in my code
Related
I have a "Bottom navigation bar app" with 3 tabs or fragments. On the first fragment I have a PaintView that I can draw on. Drawing works great. But I'm trying to wire up a clear screen function that is invoked via an options menu dropdown. When I select the option to wipe the screen from the options menu, the app crashes with the following error:
java.lang.NullPointerException: Attempt to invoke virtual method 'void com.example.mobile_testapp_android_2.ui.home.PaintView.clearView()' on a null object reference
Here is the code in the HomeFragment.java file for the options menu:
public class HomeFragment extends Fragment {
private PaintView paintView;
#Override
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
PaintView homeFragmentView = new PaintView(requireContext());
return homeFragmentView;
}
//Enable Clear Menu in this fragment
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
setHasOptionsMenu(true);
super.onCreate(savedInstanceState);
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
//inflate menu
inflater.inflate(R.menu.menu_swipes, menu);
super.onCreateOptionsMenu(menu, inflater);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
//Handle menu item clicks here
int id = item.getItemId();
if (id == R.id.action_clearScreen) {
//do clear function here:
paintView.clearView();
Toast.makeText(getActivity(), "Clear Screen", Toast.LENGTH_SHORT).show();
}
return super.onOptionsItemSelected(item);
}
}
And here is the code in the PaintView file which contains the clearView() method that I am trying to invoke:
public class PaintView extends View {
public ViewGroup.LayoutParams params;
private Path myPath = new Path();
private Paint brush = new Paint();
private boolean forceClear;
public PaintView(Context context) {
super(context);
brush.setAntiAlias(true);
brush.setColor(Color.MAGENTA);
brush.setStyle(Paint.Style.STROKE);
brush.setStrokeJoin(Paint.Join.ROUND);
brush.setStrokeWidth(8f);
params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
//Clear function
public void clearView() {
try {
forceClear = true;
invalidate();
} catch (Exception ee) {
Log.d("Clear Button: ", "We blew up!! " + ee);
}
}
I'm thinking that the problem is due to the way that I instantiate the PaintView class in the HomeFragment.java file, but I don't get any compilation errors and I can use dot notation paintView.clearView() to access the methods within the PaintView class just fine.
Any tips on what I am doing wrong would be greatly appreciated!
Here are a few screenshots:
Try to use the variables, which you define:
// PaintView homeFragmentView = new PaintView(requireContext());
this.paintView = new PaintView(requireContext());
It looks like you declare, but never actually instantiate the paintView member variable.
Try this:
private PaintView paintView;
#Override
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
this.paintView = new PaintView(requireContext());
return this.paintView;
}
Here, I have a toolbar in an Activity which contains a SearchView. And that activity has multiple fragments. One main fragment out of them have 10 more fragments inside itself. All 10 fragments are showing data in listviews. Now I'm trying to filter all the lists of fragments by SearchView of MainActivity. But it never filters list of each fragment. Now I show you how I implemented it all.
MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
final SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.action_search));
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
changeSearchViewTextColor(searchView);
return true;
}
}
Fragment.java
public class CurrencyFragment2 extends android.support.v4.app.Fragment implements SearchView.OnQueryTextListener {
#Override
public void setMenuVisibility(boolean menuVisible) {
super.setMenuVisibility(menuVisible);
if (menuVisible && getActivity() != null) {
SharedPreferences pref = getActivity().getPreferences(0);
int id = pref.getInt("viewpager_id", 0);
if (id == 2)
setHasOptionsMenu(true);
}
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.main, menu); // removed to not double the menu items
MenuItem item = menu.findItem(R.id.action_search);
SearchView sv = new SearchView(((MainActivity) getActivity()).getSupportActionBar().getThemedContext());
changeSearchViewTextColor(sv);
MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW | MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
MenuItemCompat.setActionView(item, sv);
sv.setOnQueryTextListener(this);
sv.setIconifiedByDefault(false);
super.onCreateOptionsMenu(menu, inflater);
}
private void changeSearchViewTextColor(View view) {
if (view != null) {
if (view instanceof TextView) {
((TextView) view).setTextColor(Color.WHITE);
((TextView) view).setHintTextColor(Color.WHITE);
((TextView) view).setCursorVisible(true);
return;
} else if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
changeSearchViewTextColor(viewGroup.getChildAt(i));
}
}
}
}
#Override
public boolean onQueryTextSubmit(String query) {
return true;
}
#Override
public boolean onQueryTextChange(String newText) {
if (adapter != null) {
adapter.filter2(newText);
}
return true;
}
Filter method inside Adapter class.
// Filter Class
public void filter2(String charText) {
charText = charText.toLowerCase(Locale.getDefault());
items.clear();
if (charText.length() == 0) {
items.addAll(arraylist);
} else {
for (EquityDetails wp : arraylist) {
if (wp.getExpert_title().toLowerCase(Locale.getDefault()).contains(charText)) {
items.add(wp);
}
}
}
notifyDataSetChanged();
}
You can manage the filter on nested list by using an Observable/Observer pattern, this will update each nested list from one Observable parent. I fixed all troubles and it works well now to achieve the right behaviour.
Therefore, here's what I did to achieve it:
Using one parent SearchView in Activity
(optional) Create a Filter class (android.widget.Filter) in nested list Adapter
Then, using an Observable/Observer pattern for nested Fragment with Activity
Background: When I tried your code, I had three problems:
I cannot do a search using the ActionBar: onQueryTextChange seems to be never called in Fragments. When I tap on search icon, it seems to me that SearchView (edittext, icon, etc) is not attached with the search widget (but attached to the activity's widget).
I cannot run the custom method filter2: I mean, when I resolved the previous point, this method doesn't work. Indeed, I have to play with custom class extending by Filter and its two methods: performFiltering and publishResults. Without it, I got a blank screen when I tap a word in search bar. However, this could be only my code and maybe filter2() works perfectly for you...
I cannot have a persistent search between fragments: for each child fragment a new SearchView is created. It seems to me that you repeatedly call this line SearchView sv = new SearchView(...); in nested fragment. So each time I switch to the next fragment, the expanded searchview removes its previous text value.
Anyway, after some researches, I found this answer on SO about implementing a Search fragment. Almost the same code as yours, except that you "duplicate" the options menu code in parent activity and in fragments. You shouldn't do it - I think it's the cause of my first problem in previous points.
Besides, the pattern used in the answer's link (one search in one fragment) might not be adapted to yours (one search for multiple fragments). You should call one SearchView in the parent Activity for all nested Fragment.
Solution: This is how I managed it:
#1 Using a parent SearchView:
It will avoid duplicate functions and let the parent activity supervise all its children. Futhermore, this will avoid your duplication icon in the menu.
This is the main parent Activity class:
public class ActivityName extends AppCompatActivity implements SearchView.OnQueryTextListener {
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
MenuItem item = menu.findItem(R.id.action_search);
SearchView searchview = new SearchView(this);
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
searchview.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
...
MenuItemCompat.setShowAsAction(item,
MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW |
MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
MenuItemCompat.setActionView(item, searchview);
searchview.setOnQueryTextListener(this);
searchview.setIconifiedByDefault(false);
return super.onCreateOptionsMenu(menu);
}
private void changeSearchViewTextColor(View view) { ... }
#Override
public boolean onQueryTextSubmit(String query) { return false; }
#Override
public boolean onQueryTextChange(String newText) {
// update the observer here (aka nested fragments)
return true;
}
}
#2 (optional) Create a Filter widget:
Like I said previously, I cannot get it work with filter2(), so I create a Filter class as any example on the web.
It quickly looks like, in the adapter of nested fragment, as follows:
private ArrayList<String> originalList; // I used String objects in my tests
private ArrayList<String> filteredList;
private ListFilter filter = new ListFilter();
#Override
public int getCount() {
return filteredList.size();
}
public Filter getFilter() {
return filter;
}
private class ListFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
if (constraint != null && constraint.length() > 0) {
constraint = constraint.toString().toLowerCase();
final List<String> list = originalList;
int count = list.size();
final ArrayList<String> nlist = new ArrayList<>(count);
String filterableString;
for (int i = 0; i < count; i++) {
filterableString = list.get(i);
if (filterableString.toLowerCase().contains(constraint)) {
nlist.add(filterableString);
}
}
results.values = nlist;
results.count = nlist.size();
} else {
synchronized(this) {
results.values = originalList;
results.count = originalList.size();
}
}
return results;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results.count == 0) {
notifyDataSetInvalidated();
return;
}
filteredList = (ArrayList<String>) results.values;
notifyDataSetChanged();
}
}
#3 Using an Observable/Observer pattern:
The activity - with the searchview - is the Observable object and the nested fragments are the Observers (see Observer pattern). Basically, when the onQueryTextChange will be called, it will trigger the update() method in the existant observers.
Here's the declaration in parent Activity:
private static ActivityName instance;
private FilterManager filterManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
instance = this;
filterManager = new FilterManager();
}
public static FilterManager getFilterManager() {
return instance.filterManager; // return the observable class
}
#Override
public boolean onQueryTextChange(String newText) {
filterManager.setQuery(newText); // update the observable value
return true;
}
This is the Observable class which will listen and "pass" the updated data:
public class FilterManager extends Observable {
private String query;
public void setQuery(String query) {
this.query = query;
setChanged();
notifyObservers();
}
public String getQuery() {
return query;
}
}
In order to add the observer fragments to listen the searchview value, I do it when they are initialized in the FragmentStatePagerAdapter.
So in the parent fragment, I create the content tabs by passing the FilterManager:
private ViewPager pager;
private ViewPagerAdapter pagerAdapter;
#Override
public View onCreateView(...) {
...
pagerAdapter = new ViewPagerAdapter(
getActivity(), // pass the context,
getChildFragmentManager(), // the fragment manager
MainActivity.getFilterManager() // and the filter manager
);
}
The adapter will add the observer to the parent observable and remove it when the child fragments are destroyed.
Here's the ViewPagerAdapter of parent fragment:
public class ViewPagerAdapter extends FragmentStatePagerAdapter {
private Context context;
private FilterManager filterManager;
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
public ViewPagerAdapter(Context context, FragmentManager fm,
FilterManager filterManager) {
super(fm);
this.context = context;
this.filterManager = filterManager;
}
#Override
public Fragment getItem(int i) {
NestedFragment fragment = new NestedFragment(); // see (*)
filterManager.addObserver(fragment); // add the observer
return fragment;
}
#Override
public int getCount() {
return 10;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
NestedFragment fragment = (NestedFragment) object; // see (*)
filterManager.deleteObserver(fragment); // remove the observer
super.destroyItem(container, position, object);
}
}
Finally, when filterManager.setQuery() in activity is called with onQueryTextChange(), this will be received in nested fragment in update() method which are implementing Observer.
This is the nested fragments with the ListView to filter:
public class NestedFragment extends Fragment implements Observer {
private boolean listUpdated = false; // init the update checking value
...
// setup the listview and the list adapter
...
// use onResume to filter the list if it's not already done
#Override
public void onResume() {
super.onResume();
// get the filter value
final String query = MainActivity.getFilterManager().getQuery();
if (listview != null && adapter != null
&& query != null && !listUpdated) {
// update the list with filter value
listview.post(new Runnable() {
#Override
public void run() {
listUpdated = true; // set the update checking value
adapter.getFilter().filter(query);
}
});
}
}
...
// automatically triggered when setChanged() and notifyObservers() are called
public void update(Observable obs, Object obj) {
if (obs instanceof FilterManager) {
String result = ((FilterManager) obs).getQuery(); // retrieve the search value
if (listAdapter != null) {
listUpdated = true; // set the update checking value
listAdapter.getFilter().filter(result); // filter the list (with #2)
}
}
}
}
#4 Conclusion:
This works well, the lists in all nested fragments are updated as expected by just one searchview. However, there is an incovenient in my above code that you should be aware of:
(see improvements below) I cannot call Fragment general object and add it to being an observer. Indeed, I have to cast and init with the specific fragment class (here NestedFragment); there might be a simple solution, but I didn't find it for now.
Despite this, I get the right behaviour and - I think - it might be a good pattern by keeping one search widget at the top, in activity. So with this solution, you could get a clue, a right direction, to achieve what you want. I hope you'll enjoy.
#5 Improvements (edit):
(see *) You can add the observers by keeping a global Fragment class extension on all nested fragments. This how I instantiate my fragments to the ViewPager:
#Override
public Fragment getItem(int index) {
Fragment frag = null;
switch (index) {
case 0:
frag = new FirstNestedFragment();
break;
case 1:
frag = new SecondFragment();
break;
...
}
return frag;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
ObserverFragment fragment =
(ObserverFragment) super.instantiateItem(container, position);
filterManager.addObserver(fragment); // add the observer
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
filterManager.deleteObserver((ObserverFragment) object); // delete the observer
super.destroyItem(container, position, object);
}
By creating the ObserverFragment class as follows:
public class ObserverFragment extends Fragment implements Observer {
public void update(Observable obs, Object obj) { /* do nothing here */ }
}
And then, by extending and overriding update() in the nested fragments:
public class FirstNestedFragment extends ObserverFragment {
#Override
public void update(Observable obs, Object obj) { }
}
Just to understand the question better.
1. You have a search view in the Action Bar
2. You have multiple Fragments (from your image Advisory/TopAdvisors...)
3. Within each of these there are multiple Fragments for each Tab (example Equity ... etc)
4. You want all list views in the fragments to filter their data based on the search content
Right??
In your current implementation what is the status? Is the list view which is current being displayed getting filtered?
Or even that is not working? Then you need to check notifydatasetChanged is propagating correctly to the adapter. Meaning it should be on the UIThread itself.
For updates to all fragments, meaning once which were not on screen when you were typing the search text, you need to consider the Fragment lifecycle and include code in onResume of the fragment to make sure list is filtered before it is used to initialise the adapter. This is for scenario where you have typed search text already and now are moving between tabs/fragments. So fragments would go on and off screen hence the need for onResume.
Update:
Include checking search box for text, and if it has something calling adapter.filter2() in onResume, because that is basically what is filtering your list. .
the problem is - when you launch a fragment, it "takes over" the activity's searchView and whatever you type AFTER this fragment is launched invokes the onQueryTextChange listener of JUST THIS fragment.. Hence, the search doesnt take place in any other fragment..
You want your fragments to check for the last search query (posted by any other fragment) when they are launched and perform a search in itself for that query. Also, any change in the search query must also be posted to the activity's search view so other fragments can read it when they are launched.
I'm assuming your viewpager/tabs are implemented in a way that whenever you switch the visible fragments, they get destroyed.
Make the following changes (read the comments)
public class MainActivity extends AppCompatActivity {
final SearchView searchView; //make searchView a class variable/field
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.action_search));
SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
changeSearchViewTextColor(searchView);
return true;
}
}
Now, in all your fragments do this -
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.main, menu); // removed to not double the menu items
MenuItem item = menu.findItem(R.id.action_search);
SearchView sv = new SearchView(((MainActivity) getActivity()).getSupportActionBar().getThemedContext());
changeSearchViewTextColor(sv);
MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW | MenuItemCompat.SHOW_AS_ACTION_IF_ROOM);
MenuItemCompat.setActionView(item, sv);
sv.setOnQueryTextListener(this);
sv.setIconifiedByDefault(false);
sv.setQuery(((MainActivity)getActivity()).searchView.getQuery()); // set the main activity's search view query in the fragment's search view
super.onCreateOptionsMenu(menu, inflater);
}
Also, in your SearchView.OnQueryTextListener in the fragments, do this
SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
search(query); // this is your usual adapter filtering stuff, which you are already doing
((MainActivity)getActivity()).searchView.setQuery(query);//sync activity's searchview query with fragment's searchview query, so other fragments can read from the activity's search query when they launch.
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
search(newText);// this is your usual adapter filtering stuff, which you are already doing
((MainActivity)getActivity()).searchView.setQuery(query);//sync activity's searchview query with fragment's searchview query
return false;
}
});
I hope you get the general idea
You have different search view instances in each of your fragments and the activity
you need to sync the search queries among each of them, make each of them aware of what the other instances have..
I don't recommend this design though, what i would rather do is
just use the activity's search view and in the onQueryTextChange listener of activity's search view.
i would notify all the fragments which are alive from onQueryTextChange in the activity (the fragments would register and deregister onQueryTextChange listeners with the activity in their onResume & onPause of the respective fragments). [Side note - This design pattern is called the Observer pattern]
I want to copy the text in the current TextView (within a fragment) in a ViewPager, but it copies the previous, or the text in the next TextView. I searched and found that it is because of the ViewPager's normal behaviour to create three pages: The previous, current and the next page. But I could not find how to determine the current one easily?
I use ViewPager with FragmentStatePagerAdapter.
Edit:
I have searched for this for two days but after asking my question I found nearly the same problem:
This is a better asked and explained question then mine. Atleast I learned how to ask and explain.
Wrong fragment in ViewPager receives onContextItemSelected call
but "getUserVisibleHint()" answer doesnot work for me?
...Fragment.java
onCreateView
.........
tvTextView = (TextView) v.findViewById(R.id.TextView);
tvTextView.setTextSize( TypedValue.COMPLEX_UNIT_PX, boyut);
registerForContextMenu(TextView);
.....
#Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, view, menuInfo);
if (view.getId()== R.id.tvTextView) {
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.menu_context, menu);
menu.setHeaderTitle("Copy...");
}
}
#Override
public boolean onContextItemSelected(MenuItem item) {
if( getUserVisibleHint() == false )
{
return false;
}
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
switch(item.getItemId()) {
case R.id.copy:
copy();
return true;
default:
return true;
}
}
In ..FragmentContainer.java
....
mViewPager = (ViewPager) findViewById(R.id.viewPagerLayout);
FragmentManager fm = getSupportFragmentManager();
mViewPager.setAdapter(new FragmentStatePagerAdapter(fm) {
#Override
public int getCount() {
return myArray.size();
}
#Override
public Fragment getItem(int pos) {
return ...Fragment.newInstance(name, pos, checkedItems, fontsize);
}
});
After a lot of searching and I dont know how many trial and error, I found a solution for my problem.
Using "getUserVisibleHint()" or "getGroupId()" methods, I couldnot get the current textview's text.
I used "text = tvTextView.getText().toString();" in onContextItemSelected or in onCreateView methods, but it didnot work.
#Override
public boolean onContextItemSelected(MenuItem item) {
if( ! getUserVisibleHint() )
{
return false;
}
// this is not working
text = tvTextView.getText().toString();
Then I tried making setText and getText methods for fragment and getting the text in onContextItemSelected method using getText method. It works great!
First I added two methods to Fragment:
public String getText() {
return mText;
}
public void setText(String text) {
mText = text;
}
In onCreateView I set the text
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment, parent, false);
tvTextView = (TextView) v.findViewById(R.id.tvTextView);
registerForContextMenu(tvTextView);
String tvText = tvTextView.getText().toString();
// I set the text
setText(tvText);
Then in onContextItemSelected I get it using getText
#Override
public boolean onContextItemSelected(MenuItem item) {
if( ! getUserVisibleHint() )
{
return false;
}
// I get the text
mCopyText = getText();
Both "getUserVisibleHint()" or "getGroupId()" methods works great in this way. I hope this help other people.
I have a class that extends ListActivity where the list items respond to OnClick events. Adding an OnItemLongClickListener does not work. The onItemLongClick() function is not called (no log-output or Toast showing) but the normal OnClick() event is handled instead.
I want to display a contextual action bar upon long click. A minimum example using my code in a new project works fine. So my question is: What can possibly prevent the onItemLongClick() trigger from being triggered?
My minimum API is 11. I am also setting the listView to longClickable="true".
Activity code (selected functions):
public class EventListActivity extends ListActivity {
private ArrayList<Event> arrEvents = null;
private ArrayAdapter<Event> adpEvents = null;
private ActionMode mActionMode = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// only create list adapter and set it
arrEvents = new ArrayList<Event>();
adpEvents = new ArrayAdapter<Event>(this, android.R.layout.simple_list_item_activated_2, android.R.id.text1, arrEvents) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
TextView text1 = (TextView) view.findViewById(android.R.id.text1);
TextView text2 = (TextView) view.findViewById(android.R.id.text2);
text1.setText(arrEvents.get(position).getTitle());
text2.setText(arrEvents.get(position).getDateTimeFormatted());
return view;
}
};
setListAdapter(adpEvents);
// add CAB to ListView
setupCAB();
}
#Override
protected void onResume() {
super.onResume();
// populate list and refresh adapter
createEventList();
adpEvents.notifyDataSetChanged();
// if list empty show emtpy msg, otherwise hide it
setContentView(R.layout.activity_event_list);
TextView empty = (TextView) findViewById(R.id.text_empty);
if(arrEvents.isEmpty()) {
empty.setVisibility(View.VISIBLE);
} else {
empty.setVisibility(View.GONE);
}
}
private void setupCAB() {
// Important: to select single mode
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
// Called when the user long-clicks an item on the list
#Override
public boolean onItemLongClick(AdapterView<?> parent, View row, int position, long rowid) {
Log.w("EventListActivity", "Long click detected!");
Toast.makeText(EventListActivity.this, "Long click detected!", Toast.LENGTH_SHORT).show();
if (mActionMode != null) {
return false;
}
// Important: to mark the editing row as activated
getListView().setItemChecked(position, true);
// Start the CAB using the ActionMode.Callback defined above
mActionMode = EventListActivity.this.startActionMode(mActionModeCallback);
return true;
}
});
}
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
// Called when the action mode is created; startActionMode() was called
#Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// Inflate a menu resource providing context menu items
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.event_context, menu);
return true;
}
// Called when the user enters the action mode
#Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// Disable the list to avoid selecting other elements while editing one
EventListActivity.this.getListView().setEnabled(false);
return true; // Return false if nothing is done
}
// Called when the user selects a contextual menu item
#Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.mnu_share_event:
//TODO share event
mode.finish();
return true;
default:
return false;
}
}
// Called when the user exits the action mode
#Override
public void onDestroyActionMode(ActionMode mode) {
// Re-enable the list after edition
EventListActivity.this.getListView().setEnabled(true);
mActionMode = null;
}
};
}
activity_event_list.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=".EventListActivity" >
<TextView
android:id="#+id/text_empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="45dp"
android:text="#string/empty"
android:textAppearance="?android:attr/textAppearanceLarge"
android:visibility="gone" />
<ListView
android:id="#android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:longClickable="true" >
</ListView>
</RelativeLayout>
If you have buttons responding to onClick() events inside your listview, you need to set the following in the container holding those buttons:
android:descendantFocusability="blocksDescendants"
If what you have are textviews, the problem is slightly trickier. See this: Focusable EditText inside ListView
This answer does not solve user1's question but the symptoms were similar my problem (i.e. OnItemClickListener was getting called but OnItemLongClickListener was not). I'm posting my answer here in case anyone else stumbles on this question like I did when trying to solve my problem.
I was using a ListView inside a Fragment and implemented the listener methods:
public class MyFragment extends Fragment implements OnClickListener,
OnLongClickListener, OnItemClickListener, OnItemLongClickListener {
Here is the onItemClick method that was working fine:
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long rowId) {
Log.i("Chimee", "short click working");
}
And here is the onItemLongClick method that wasn't firing:
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position,
long rowId) {
Log.i("Chimee", "Long click working");
return false;
}
Of course the simple answer was that I forgot to setOnItemLongClickListener. I added it after the setOnItemClickListener that I had all along and then it worked fine.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.my_fragment, container, false);
lvSuggestions = (ListView) v.findViewById(R.id.lvSuggestions);
lvSuggestions.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
lvSuggestions.setOnItemClickListener(this);
lvSuggestions.setOnItemLongClickListener(this); // Forgot this
...
}
When using a ListActivity or ListFragment there is no method you can override for the long-click, and getting access to the ListView is not possible in onCreateView(), since it is being controlled by the parent class.
So, to overcome this, I did this, since the getListView() command won't work until after the view is created:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mRecipeListView = this.getListView();
mRecipeListView.setOnItemLongClickListener(new ListView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> arg0, View view, int position, long row_id) {
// Process the long-click
}
});
}
This is my situation:
I have several fragments added dynamicly to an FragmentStatePagerAdapter, this works fine. But now i want to be able to replace an fragment when I push on an button.
public class QuestionFragment extends UpperFragment {
...
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setRetainInstance(true);
CustomViewPager.enabled = true;
View rootView = inflater.inflate(R.layout.question, container, false);
Button btn = ((Button) rootView.findViewById(R.id.bQuestion));
if (how == true) {
btn.setVisibility(View.VISIBLE);
btn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// lijst afgaan met alle how en kijken welke id nodig is
for (int i = 0; i < XmlParserSax.howFragments.size(); i++) {
Fragment how = XmlParserSax.howFragments.get(i);
if (howId.equals(((UpperFragment) how).getIdNum())) {
FragmentTransaction transaction = getFragmentManager()
.beginTransaction();
transaction
.replace(R.id.flQuestion, how, "howFragment")
.setTransition(
FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.addToBackStack(null).commit();
break;
}
}
}
});
} else {
btn.setVisibility(View.GONE);
}
return rootView;
}
So when i press the button the current layout (R.id.flQuestion) is replaced with the new fragment. This works, but the tricky part comes here:
When I slide to the next fragment and the slide to the fragment with the button it keeps working but if i slide 2 times to the next fragment (of the same type QuestionFragment) it does the functionallity of the new fragment but it doesn't show the new fragment.. So it seems that it can't replace the R.id.flQuestion because it is stored in memory maybe?
I need to be sure that the fragment is always replaced even if the next 2 fragments are of the same type and same layout (R.id.flQuestion)..
This is the class layout of the new frag
public class HowFragment extends UpperFragment {
..
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
setRetainInstance(true);
View rootView = inflater.inflate(R.layout.how, container, false);
//if back key pressed return to layout of Question
rootView.setFocusableInTouchMode(true); //this line is important
rootView.requestFocus();
rootView.setOnKeyListener( new View.OnKeyListener()
{
#Override
public boolean onKey( View v, int keyCode, KeyEvent event )
{
if( keyCode == KeyEvent.KEYCODE_BACK )
{
CustomViewPager.enabled = true;
return false;
}
return true;
}
} );
//don't allow pushing button again
rootView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
return true;
}
});
return rootView;
}
Also important to tell: i'm using framelayouts for both the fragments (so no hard coded fragment tag in the xml)
To make it clear:
This happens when the next fragment is from different class, no problem
This happens when next fragments are from the same layout and class:
I'll have to test the code, but from what I see, easiest way to make this work is to grab new Fragment even if it is the same fragment class.
Fragment how = XmlParserSax.howFragments.get(i);
If this function is returning new instance of a fragment, it should work.
Hope that helps
Edit :
I'm pretty sure the activity can access the button after the fragments are created.
Otherwise you need a handler to pass the click to handle it in the adapter. I'm seeing the your list of fragments are static (Not recommended). Since you haven't added any codes for how you setup the pageradapter, I have no idea what list you are using, but you need to change out the item in that list. From the Activity where you initialized the pager, you can call the public function to replace the current pager item pager. (you can use ViewPager.getCurrentItem())
I haven't tested, so you might have to tweak and play around to perfect it.
Hope this helps.
Here is a sample :
public static class MyAdapter extends FragmentStatePagerAdapter {
ArrayList<Fragment> fragmentArray = new ArrayList<Fragment();
public MyAdapter(FragmentManager fm) {
super(fm);
}
//didn't put in the function to populate the list with fragments
public void replaceItem(Fragment newFrag, int pos){
fragmentArray.remove(pos);
fragmentArray.add(pos,newFrag);
notifyDataSetChanged();
}
#Override
public int getCount() {
return fragmentArray.size();
}
#Override
public Fragment getItem(int position) {
return fragmentArray.get(position);
}
}