I've got a TabLayout (from the Design Library) that is populated with 2 fragments (that both hold a list of items) through a FragmentPagerAdapter.
The 2 lists are in connection with each other: They both hold a total of persons and in which list a person is, decides whether he/she is nominated or not.
You can swipe members from one list to the other and vice versa.
The problem
I want/need to show a count (of how many persons are in that particular list) in the tab titles.
Easy enough, I add a bit of code in getPageTitle and it works fine.
Until I change the lists, and I need to tell the FragmentPagerAdapter to update its titles again: It won't.
So far, the only solution I found was to call: mTabLayout.setTabsFromPagerAdapter(mAdapter);
All this method does, is removeAllTabs and then loop through the ones the FragmentPagerAdapter has, adding them as new.
This works for the titles, but isn't very efficient, but most importantly: It forces the first tab to become the selected item again, which isn't a nice experience when swiping from the 2nd tab.
I've tried adding mViewPager.setCurrentItem(int item) after the mTabLayout.setTabsFromPagerAdapter(mAdapter); call, but that does not work.
I also tried things like
mAdapter.notifyDataSetChanged();
tabLayout.invalidate();
With no effect.
Here is how i am doing, I had requirement in which i need to set some count to tab title everytime.
I used custom layout for tab title. here is my custom_tab layout
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/tab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/colorAccent"
android:textSize="#dimen/tab_label" />
TO setup title first time i use my method setupTabTitle("TAB 1",0);
private void setupTabTitle(String title,int pos) {
TextView title = (TextView) LayoutInflater.from(this).
inflate(R.layout.custom_tab, null);
title .setText(title);
// set icon
// title .setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.ic_tab_favourite, 0, 0);
tabLayout.getTabAt(pos).setCustomView(title);
}
To update tab-title next time i use tabLayout.getTabAt() object
public void updateCustomTabTitle(String title,int pos) {
TextView tabTitle = (TextView) tabLayout.getTabAt(pos).getCustomView();
tabTitle.setText(title);
}
and this is how i call updateCustomTabTitle method
updateCustomTabTitle("My tab title to update",tabLayout.getSelectedTabPosition());
To get tab-title value
public String getCustomTabTitle(int pos) {
TextView tabOne= (TextView) tabLayout.getTabAt(pos).getCustomView();
return tabOne.getText().toString();
}
note: first i tried setupTabTitle method to update tab-title but it does not update title, it append new text at end of title. so i get View from tab-layout and use it to update and read tab value.
Hope this will help you.
Related
My activity has a spinner and an empty container where fragments should be added when selecting drop down items from spinner.
My plan was to try to make switch construction inside into override method "public void onItemSelected()", where each case represents one drop down item from spinner, and sets correct fragment into container, like this:
String itemSelectedFromSpinner = parent.getSelectedItem().toString();
switch (itemSelectedFromSpinner) {
case "first item": // First and second item put same fragment into container, but do other methods when used
case "second item": // my code
}
My other taught was to put it in if construction like this:
String itemSelectedFromSpinner = parent.getSelectedItem().toString();
if (itemSelectedFromSpinner.equals("first item") || itemSelectedFromSpinner.equals("second item")){
// my code }
Since I've never done something like this, and I believe you can understand from my question what needs to be done, tell me what is the best practice to do that.
Am I doing it right by putting a String itemSelectedFromSpinner into switch construction? Also if user selects one item and first fragment is loaded, when selecting other item will the first fragment disappear and put second fragment into container automatically? (sorry if this sound little silly to you, I have lack of experience with fragments)
Don't use string like "first item" in code directly - move them to string resources.
For components like spinner use Adapter. The concept of Adapters use widely in Android so it's a good idea to be familiar with it. And also it allow you to compare your data by some integer asigned id's, and not by strings (which is unefficient, slow and ugly - correcting string representation everywhere is hard).
To replace or add Fragments dynamically use FragmentManager. See the simple replace() / add() / commit() code
I am writing an android app where I am using a grid view to display some items. I want to give users a configurable view where they can change the no of columns on the activity by clicking floating action button. I am changing the column no using
gridView.setNumColumns(selectedColumnNo);
This is working fine But the problem is if a user changes no of column after some scrolling the First Visible Position is set to the first item of the array list, so the user has to scroll the view again. Can someone please tell me where I am doing wrong. Or Is this the proper way to do this or should I use a different approach.
A code snippets will be helpful
Thanks.
Update::
currently I am using the bellow snippets
findViewById(R.id.fab).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int firstPosition = gv.getFirstVisiblePosition();
if(gv.getNumColumns()==2)
{
gv.setNumColumns(1);
gv.setSelection(firstPosition);
}
else {
gv.setNumColumns(2);
gv.setSelection(firstPosition);
}
}
});
Now the problem is on every 4th switch grid view is showing the first element of the arraylist
Right before you call setNumColumns(), save the GridView's first visible position:
int firstPosition = gridView.getFirstVisiblePosition();
Then, after you change the number of columns, pass that integer to setSelection():
gridView.setSelection(firstPosition);
"Selection", counter-intuitively, is not the same thing as "activation". It will ensure that the view is on-screen, but not visibly affect it in any other way.
Consider the following overly simplified example. I have a LinearLayout with a list of items (EditTexts). The number of items is not known at compile time (for example it might be a game menu screen where the user has just selected the number of players and on this screen they are entering each individual player's name).
The obvious way to do this is to have a container layout defined in xml (R.layout.list), and individual item layouts defined in xml (R.layout.item); which would look something like this:
public class Main extends Activity {
private static final String FRAG_TAG = "ITEMS_FRAGMENT";
private static final int NUM_ITEMS = 3;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout ll = (LinearLayout) getLayoutInflater().inflate(R.layout.list, null, false);
for (int i = 0; i<NUM_ITEMS; i++) {
ll.addView(getLayoutInflater().inflate(R.layout.item, null, false));
}
setContentView(ll);
}
}
With the R.layout.list and R.layout.item layouts respectively:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/list" />
<EditText xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/item"
android:singleLine="true"/>
The problem is that on an orientation change, the fact that all items have the same ID (R.id.item) confuses the restoration process. The end result is that the state of the last item is restored to all items in the list (i.e. if the last item's EditText has "hi" in it, every EditText will have "hi" in it after the orientation chance).
In the instance of this in my code, each item has a CheckBox that I can get the state of with a isCheckboxChecked(LinearLayout list, int itemNumber) function that relies on the fact that each item has a view with ID R.id.checkbox. Is there a proper way to do view state restoration that accepts there may be multiple items inflated from the same XML resource (and consequently will have the same ID)?
(ListView is not appropriate for this case, but it handles this correctly. How does it restore state correctly when all of it's items typically have the same ID?)
Android stores states by associating them with element's ID. If all elements have same ID, then a state associated with this ID will be restored to all elements with this ID. LisView does it very differently. It always reads values from Adapter by element position. Because views with the same ID will have different position in the list, they will have different values too.
If your case you have couple of options.
1) You need to assign different ID'S to your items before adding them to the LinearLayout by calling setId() method. You need to define those ID's in Android resources firs (e.g. <item type="id" name="item_01" /> etc.).
2) You need to override onSaveInstanceState(Bundle outState), go through the children of LinearLayout and store their texts into outState bundle associating text with children's index. Then in onRestoreInstanceState() you need to read states from savedInstanceState and set them to your views correspondingly.
3) You use ListView and when user enters text, your adapter persists data (e.g. into a file or database). When configuration changes, or on the next start of Activity your adapter will read those stored values, and they will be restored properly.
Hope one of the options can help you.
My application uses a Multi Pane layout to display a list of assignments. Each Assignment can be put in one AssignmentCategory. I want to use a DrawerLayout to display all the AssignmentCategories so the user can switch easily between the diffirent categories.
I didn't manage to create such a layout. In the official DrawerLayout tutorial the DrawerLayoutActivity replaces a Fragment when a user clicks on a item (in my case an AssignmentCategory). The problem I facing is that a Multi Pane layout requires a FragmentActivity. I don't know how to create a Fragment which contains a Multi Pane layout. Did someone manage to do this?
Combining the two projects shouldn't be too difficult. In the sample code the DrawerLayout example does replace the content fragment but you don't have to do the same, you could simply update the same fragment to show the proper data. You could implement the two projects this way:
start from the multi pane demo project.
update the two activities of the multi pane demo to extends ActionBarActivity(v7), you don't need to extend FragmentActivity
implement the DrawerLayout(the sample code from the drawer project) code in the start list activity(I'm assuming you don't want the DrawerLayout in the details activity, but implementing it shouldn't be a problem if you want it).
the layout of the start list activity will be like this(don't forget that you need to implement the DrawerLayout changes in the activity_item_twopane.xml as well!):
<DrawerLayout>
<fragment android:id="#+id/item_list" .../>
<ListView /> <!-- the list in the DrawerLayout-->
</DrawerLayout>
change the implementation DrawerItemClickListener so when the user clicks the drawer list item you don't create and add a new list fragment, instead you update the single list fragment from the layout:
AssignmentListFragment alf = (AssignmentListFragment) getSupportFragmentManager()
.findFragmentById(R.id.item_list);
if (alf != null && alf.isInLayout()
&& alf.getCurrentDisplayedCategory() != position) {
alf.updateDataForCategory(position); // the update method
setTitle(DummyContent.CATEGORIES[alf.getCurrentDisplayedCategory()]);
}
the update method would be something like this:
/**
* This method update the fragment's adapter to show the data for the new
* category
*
* #param category
* the index in the DummyContent.CATEGORIES array pointing to the
* new category
*/
public void updateDataForCategory(int category) {
mCurCategory = category;
String categoryName = DummyContent.CATEGORIES[category];
List<DummyContent.Assigment> data = new ArrayList<Assigment>(
DummyContent.ITEM_MAP.get(categoryName));
mAdapter.clear(); // clear the old dsata and add the new one!
for (Assigment item : data) {
mAdapter.add(item);
}
}
public int getCurrentDisplayedCategory() {
return mCurCategory;
}
-various other small changes
I've made a sample project to illustrate the above changes that you can find here.
I have a main activity that uses a FrameLayout with the screen split into left navigation and right content panels. The left panel is used for navigation to load different layouts into the right panel (called contentLayout) which can have multiple layouts glued together. I have a ListView in the left navigation panel with onItemClickListeners. In their separate onItemClick methods, I use contentLayout.removeAllViews() and then inflate their layouts like so:
private final OnItemClickListener redListener = new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
contentLayout.removeAllViews();
if (myFirstClassLayout == null) {
myFirstClassLayout = FirstClassLayout.getLayout(
layoutInflater,
MyActivity.this,
resources,
new ArrayList<String>(),
new ArrayList<Desc>()
);
}
CommonClass.updateListeners();
if (FirstClassLayout != null) contentLayout.addView(myFirstClassLayout);
}
};
private final OnItemClickListener blueListener = new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
contentLayout.removeAllViews();
if (mySecondClassLayout== null) {
mySecondClassLayout = SecondClassLayout.getLayout(
layoutInflater,
MyActivity.this,
resources,
new ArrayList<String>(),
textList,
new ArrayList<Desc>(),
hash
);
}
CommonClass.updateListeners();
if (SecondClassLayout != null) contentLayout.addView(mySecondClassLayout);
}
};
In both the FirstClassLayout and SecondClassLayout, I inflate a ThirdClassLayout which contains a dynamic ListView (let's call it commonListView) that makes use of an ArrayAdapter<String>. The ListView is a listChoiceIndicatorSingle style view, meaning that it shows a radio-button-like icon to the right of each list item.
When the app starts up, the right content panel is empty. If I trigger the redListener or the blueListener, it will display the commonListView in the right content panel with none of the list items selected. If I trigger red first, then trigger blue, then make a selection in the commonListView, the change is reflected back to the FirstClassLayout which is great! However, when I continue by selecting something else in the commonListView from that FirstClassLayout, and then trigger the blueListener to go to the SecondClassLayout, the commonListView in the SecondClassLayout does not reflect the selection I just made. Instead, it is stuck on item I selected when I was last on the SecondClassLayout.
This happens if I perform the testing in the reverse order, so depending on which listener I trigger last, that last listener and layout will have proper UI synching control over both layouts. The one I trigger first seems to be able to only control itself. I need for both listeners to be able to display the exact same ListView selected item on both Layouts at any given time.
The synching of the commonListView from last layout to first layout is achieved through CommonClass.updateListeners() which does:
adapter.clear();
for (String title: statusSet) adapter.add(title);
adapter.notifyDataSetChanged();
commonListView.setItemChecked(positionOfItemChecked, true);
Log.i(TAG, "position checked = " + positionOfItemChecked);
adapter.notifyDataSetChanged();
Since this works from last layout triggered to first, I figure this must be related to a UI refresh problem, but I've searched everywhere for a good solution and tried all suggestions. The Log.i statement prints out correctly in the LogCat for first or last layout triggered, so I know it is hitting the code properly. I just don't understand why the first one is unable to control the last one. I've tried:
commonListView.invalidate();
((BaseAdapter) commonListView.getAdapter()).notifyDataSetChanged();
and AsyncTask and several others, but nothing seemed to work on refreshing the first layout so that it matches the second. Has anyone encountered such an issue? Does anyone know how to fix or get around this?
See if contentLayout.requestLayout() after adding the views fixes your problem.
I figured out what was causing the 2nd one to take hold over the 1st. I inherited a lot of this code and so I'm not familiar with all the parts. Several other pieces were involved. I finally found the portion of code where the original author used a custom AdapterWrapper that implements a custom Listener. The ThirdClassLayout was only creating a new ArrayAdapter and a new AdapterWrapper once by checking to see if the static ArrayAdapter was null. Removing this check allowed both adapter objects to be created new for both layouts. With their own Listeners created, both layouts now respond.