Can I disable ActionBar's navigation Spinner? - android

What I want to achieve is disabling all items on ActionBar except one. I have a custom ActionBar with Menu,several TextViews, one Button and a Spinner from ListNavigation.
Spinner is created because of bar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); like this :
SpinnerAdapter spinnerAdapter = new ArrayAdapter<String>(this.getActivity(), R.layout.action_bar_spinner, names);
// "listener" for spinner
bar.setListNavigationCallbacks(spinnerAdapter, new OnNavigationListener() {
#Override
public boolean onNavigationItemSelected(int position, long itemId) {
// do some stuff
return true;
}
});
I want to disable the Spinner, so that it is inactive, but still visible with last item selected before disabling it.In short I need something like bar.getNavigationSpinner.setEnabled(false) if it existed. Question is: Is there some kind of workaround ? If not, is there a way to disable whole ActionBar,but keep it visible ?
Note: I want to achieve it in a Fragment.

Yes, it is possible to disable the Spinner used in list navigation in ActionBar. But is't not a straightforward solution, rather a hack. ActionBar doesn't provide a direct access to the Spinner view. Unfortunately the Spinner is created in a private code without any id.
So how to access the Spinner instance? One solution could be to access it via Java reflection API, but I wouldn't recommend that.
A better solution is to get the root view for the current Activity, traverse it's child views (action bar and all it's views are present in the view hierarchy) and find the proper Spinner. Since the Spinner in action bar is presumably the only one you haven't created yourself, you should be able to distinguish it from the others.
Getting the root View is described in this SO question.
The traversal is rather simple, just bear in mind that if you are using ActionBarSherlock, you have to look for IcsSpinner instead of Spinner (IcsSpinner does not extend from Spinner).
private View findActionBarSpinner() {
View rootView = findViewById(android.R.id.content).getRootView();
List<View> spinners = traverseViewChildren( (ViewGroup) rootView );
return findListNavigationSpinner(spinners); // implement according to your layouts
}
private List<View> traverseViewChildren(ViewGroup parent) {
List<View> spinners = new ArrayList<View>();
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
if (child instanceof Spinner) {
spinners.add( child );
} else if (child instanceof IcsSpinner) { // add this if you are using ActionBarSherlock
spinners.add( child );
} else if (child instanceof ViewGroup) {
spinners.addAll( traverseViewChildren( (ViewGroup) child ) );
}
}
return spinners;
}
The function findListNavigationSpinner should be implemented in a way that you are able to distinguish the other spinners. If you are not using any Spinner (or any view derived from it), the returned list should contain just one item.
The code above describes how to get the Spinner in an Activity. Naturally, it is not a problem to disable the Spinner from within a Fragment. The fragment has a reference to it's activity, so the activity can expose the code to the fragment via some interface.

The above code does not work with Action Bar compat (v7 support library). I needed to enable/disable the action bar spinner of my app (API 2.2) using this support library but I have noticed that the name of the class is SpinnerICS instead Spinner.
The full package name is
android.support.v7.internal.widget.SpinnerICS
Because of this class is not visible and can not be imported in my code, I can not use:
if (child instanceof SpinnerICS) ...
So finally I solved my issue using getClass().getName. Therefore the code for enabling/disabling the spinner using Action Bar compat support library (v7) will be:
private List<View> traverseViewChildren(ViewGroup parent) {
List<View> spinners = new ArrayList<View>();
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
String className = child.getClass().getName();
if (child instanceof Spinner || className.equals("android.support.v7.internal.widget.SpinnerICS")) {
spinners.add( child );
} else if (child instanceof ViewGroup) {
spinners.addAll( traverseViewChildren( (ViewGroup) child ) );
}
}
return spinners;
}
Also, I have modified my findListNavigationSpinner method to check if the spinners list has elements to prevent ArrayIndexOutOfBoundsException. Because I only have one spinner in my app (in the Action Bar compat), so my code is this (you should adapt it if you would have more than one spinner):
private View findListNavigationSpinner(List<View> spinners) {
// We only have one spinner in the app
View result = null;
if (spinners != null && spinners.size() > 0) {
result = spinners.get(0);
}
return result;
}

This can be simplified by noting that the ActionBar (and Spinner) is between the root view and the content (with id == android.R.id.content). So you can implement a breadth-first search and return the first Spinner you find, no longer adding to the queue once you hit the view with id == android.R.id.content.
Or do the depth-first search, but stop recursing when you hit the view with id == android.R.id.content as the Spinner isn't contained in there.
So there's no need to maintain any list, just return the first Spinner found.

Related

ExpandableListView child views positioning on group expand or scroll

I'm working on implementing an ExpandableListView with my custom Adapter that extends BaseExpandableAdapter .
Working with the MVP model i successfully inflate all the groups and views in the right way. Piece of my GetChildView()
public override View GetChildView(int groupPosition, int childPosition, bool isLastChild, View convertView, ViewGroup parent)
{
BetViewHolder holder = new BetViewHolder(_BetPresenter);
var view = convertView;
if (view != null)
holder = view.Tag as BetViewHolder;
else
{
LayoutInflater layoutInflater = (LayoutInflater)_Context
.GetSystemService(Context.LayoutInflaterService);
holder = new BetViewHolder(_BetPresenter);
view = layoutInflater.Inflate(Resource.Layout.live_bet_view_item, null);
holder.InitEventViews(view);
view.Tag = holder;
}
_BetPresenter.OnBindBetEventView(holder, groupPosition, childPosition);
return view;
}
Inside the presenter at OnBindBetEventView i set the data on the corresponding view.
The problem
Each child item has a 3 custom components(Buttons) which are selectable. And in general each child view is relatively complex. When i select one of the buttons of the child view it successfully changes the color like it should, but also other child views from other groups change get selected too. The strange part comes when i scroll or collapse/expand a group.
The selected views are changing and some others are getting selected instead. 'Playing' with the groups it messes the all the selected buttons without a specific pattern.
Clicking the first button also checks another button on another group. It was not already selected by me. Scrolling and expanding groups changes the order of selected buttons
NOTE
After lots of debugging i realized that every 2 buttons the Unique view id's of the buttons are duplicate. Meaning that the instance of the view is exactly the same. Maybe is due to recycling or something. The point is that i need to preserve the state of my selected views without letting the adapted change the position of my views.
The recycling mechanism of the ExpandableList was causing this strange behavior due to the reuse-ability of the views. The ChildView with the clicked item was recycled once it was out of screen and inflating it again on a different position
I realize that due to the fact that the views had exactly the same unique hash created by Android. There was a duplication
I solved it by checking my model's state everytime the adapter tries to inflate the view, on the position it tries to inflate it.
Here is the ViewHolder method called from the adapter at GetChildView()
public void SetEventToView(Event eventItem)
{
txtCompetitor1.Text = eventItem.Competitor1;
txtCompetitor2.Text = eventItem.Competitor2;
txtTime.Text = eventItem.Time;
txtScore.Text = eventItem.Score;
for (int i = 0; i < _OddButtonList.Count; i++)
{
_OddButtonList[i].OddID = eventItem.BetItems[i].ID;
_OddButtonList[i].SetPriceText(eventItem.BetItems[i].BetOdd);
_OddButtonList[i].SetOnClickListener(this);
SetButtonState(eventItem, _OddButtonList[i], i);
}
}

How to get the parent tab in a View pager

I have an app with four tabs each tab has some Edit Text, some of them need to be populated in order to perform a DB operation, so when the user press the "Save changes" button a method check those Edit text and should focus you to the first that is empty,this work fine but with a problem: the requestFocus(); will not change the tab if the Edit Text is in other one.
Then I'm trying to use ViewPager.setCurrentItem(int) to change the tab before requestFocus(); but what i cant figure out is:
How to get the Edit text's tab?
The ViewPager class doesn't have a method for this.
A quite hacky solution can be that (assumes that you are using FragmentStatePagerAdapter):
Call getParent() on EditText the required number of times for having the parent Fragment. So, if you have a structure like this:
Fragment --> LinearLayout --> EditText you should call getParent() two times. Then cast the ViewParent to Fragment. You can actually cycle with a while loop until you found a ViewParent that is an instance of Fragment
Cycle through your adapter fragments
for ( int i = 0; i < adapter.getCount(); i++) {
if (adapter.getItem(i) == parentTakenBefore) {
viewPager.setCurrentItem(i);
break;
}
}
and check whether or not the current item is your page
It's more an hack than a real solution, but should works
Hope it helps
UPDATE
Added code for cycling through parents
boolean isFragment = false;
Fragment fragment = null;
ViewParent currentParent = v.getParent();
while(!isFragment && currentParent != null){
if (currentParent instanceof Fragment) {
fragment = (Fragment) currentParent();
isFragment = true;
}
else {
currentParent = currentParent.getParent();
}
}
Because for some reason neither View pager or adapter seem to recognize their child I finally came up with a solution.
First create a list of the views which added to the pager
static ArrayList<View> tabs= new ArrayList<>();
Then when add a new tab also add it to the list
tab= inflater.inflate(R.layout.any_tab, container, false);
tabs.add(tab);
Finally implement a similar loop like the suggested but this also search for parents
//e=The empty text view;
View v=e;
Boolean b=true;
int ctrl=0;
while(b){
v=(View)v.getParent();//this keep getting the parent view until reaches the tab
ctrl=0;
for(View t:tabs){
if(v==t){
b=false;
break;}
else{ctrl ++;}
}
}
mViewPager.setCurrentItem(ctrl);//this place you in the right tab
e.requestFocus();//this focus the text view

How to repaint listview items when changing orientation

I'm trying to perform something like the GMail app does: when long touching several List view items their background colour is changed so that the user notices that something happened.
My problem is to keep those items visually selected (with the background colour changed) when the device orientation is changed. I tried this with little luck:
ListView lv = getListView();
ItemAdapter adapter = (ItemAdapter)lv.getAdapter();
if (adapter == null) return;
for (Item q : mSelectedItems) {
int position = adapter.getItemPosition(q);
if (position == -1) continue;
View itemView = lv.getChildAt(position);
if (itemView == null) continue;
itemView.setBackgroundColor(getResources().getColor(R.color.orange));
}
I call this method from the onRestoreInstanceState() callback, but I receive a null in itemView because it seems that the listView hasn't been filled up yet, so there are no children at the moment. I tried to call this method from the onResume with no luck as well.
What's the proper way to accomplish this?
I found the solution by myself. The proper way to accomplish this is the custom Adapter created to fill the view. Create the adapter providing a context (the Activity that has the information of which items should be painted this way or that way) and call it to get the items whose background must be changed. When iterating on the listview to paint, if the current item is one of those, paint it differently. Example:
public View getView(int position, View convertView, ViewGroup parent) {
...
RelativeLayout itemLayout =
(RelativeLayout) LayoutInflater.from(mContext).inflate(R.layout.my_layout, parent,
false);
if (((MyActivity) mContext).isItemSelected(item)) {
itemLayout.setBackgroundColor(mContext.getResources().getColor(R.color.orange));
}
}

Odd CheckedTextView Behavior

Okay, bear with me this is a very odd & complex issue and I'm having a hard time explaining it. I'm using Xamarin.Android (Monodroid) although the source of the issue is likely an android thing.
I have an Activity which pages through Fragments manually by adding and removing them.
next = Fragments[nextIndex];
FragmentTransaction ftx = FragmentManager.BeginTransaction ();
ftx.SetTransition (FragmentTransit.FragmentFade);
ftx.SetCustomAnimations (Resource.Animator.slide_in_right, Resource.Animator.slide_out_left);
ftx.Remove (CurrentFragment);
ftx.Add (Resource.Id.fragmentContainer, next, next.Tag);
ftx.Commit();
The Fragments have a LinearLayout, which is populated with Row Views at run-time. (ListViews introduced too many focus issues with validating text entry).
// Manual ListView
this.Layout.RemoveAllViewsInLayout ();
for (int n = 0; n < Adapter.Count; n++)
{
View view = Adapter.GetView(n,null,Layout);
if (view != null) {
if (view.Parent != Layout) {
Layout.AddView(view);
}
}
}
Some of these Row Views have within them a GridView. (The gridview does not scroll)
TextView title = view.FindViewById<TextView>(Resource.Id.titleView);
GridView grid = view.FindViewById<GridView>(Resource.Id.gridView);
if (grid.Adapter == null) {
InlineAdapter adapter = new InlineAdapter(view.Context, list, this.Adapter);
// Set Grid's parameters
grid.Adapter = adapter;
grid.OnItemClickListener = adapter;
grid.SetNumColumns(adapter.GetColumns(((Activity)view.Context).WindowManager));
grid.StretchMode = StretchMode.StretchColumnWidth;
}
grid.ClearChoices();
// Get Current Value(s)
if (list.Mode == ListMode.SingleSelection)
{
grid.ChoiceMode = ChoiceMode.Single;
for (int b = 0; b < list.Selections.AllItems.Count; b++)
{
grid.SetItemChecked(b, false);
}
grid.SetItemChecked(list.GetSelectedIndex(DataStore), true);
}
The grid views have in them CheckedTextViews (either single or multiple choice).
// From "InlineAdapter"
public override View GetView(int position, View convertView, ViewGroup parent)
{
GridView grid = ((GridView)parent);
int layout = (list.Mode == ListMode.MultiSelection) ? Resource.Layout.RowListInclusive : Resource.Layout.RowListExclusive;
CheckedTextView view = (CheckedTextView)convertView;
if (view == null)
{
view = (CheckedTextView)inflator.Inflate(layout, parent, false);
}
view.Text = items[position];
// Calabash
view.ContentDescription = list.ContentDescription + "." + Selections.ContentDescription(items [position]);
return view;
}
When a fragment is presented the first time (before it is removed) everything performs as expected.
When a fragment is presented the second and subsequent times (after it is removed and re-added) only the last grid view accepts user input. In addition, whatever is selected on this last grid view ALL of the grid views select. (e.g. if the last one selects choice 2, then all of the grid views change to selecting choice 2)
I have:
Verified that the underlying data is correct
Verified using .GetHash() that all of the CheckedTextViews, GridViews, and Adapters are unique for their given rows.
Verified that touches propagate to the correct CheckedTextViews, and modify the correct data.
Verified that NotifyDataSetChanged() is called for the correct GridView.
I am personally stumped to gump on this one.

Android customized row in listview can't recieve onItemClick

because I need to display something more than just a list in a Fragment.
So I choose Fragment rather than ListFragment, and my layout is something looks like
<linearlayout...>
<TextView...>...<TextView/>
<Button...>...<Button/>
<ListView android:id = "#"+id/mylist" ...></ListView>
</linearylayout>
And I implemnt "MyAdapter" extend BaseAdapter, which has getView like following
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
if(position == 0)
{
return categroyView("Team leader");
}
else if (position == 2)
{
return categroyView("Team memebers");
}
else
{
LayoutInflater inflater = context.getLayoutInflater();
View v = inflater.inflate(R.layout.row_group, null, false);
return v;
}
}
protected View categroyView(String text)
{
TextView txtView = new TextView(context);
txtView.setText(text);
return txtView;
}
It turns out that I can receive onItemClick when its position is 0 or 2 (which as you can see I dynamically generate textView.
Meanwhile I can't receive onItemClick when its position is not 0 or 2 (which I return inflate view from XML)
I've seen some discussion about if customized row layout has some clickable item (like button), this situation will happen, but even my row layout has only one textView, it still failed to receive onItemClick.
p.s.
Also, I select Fragment rather than Activity for other other design issue.
I know I can alternatively add v.setOnClickListene in getView to help this issue, but then still the item won't highlight if I pressed on it.
What is in the position two view? If that thing might be able to take focus sit will do it instead of the list item if you don't want the inards to be clickabke then disable that it's click and it will the be passed to the item
Also are these long lists? You will run into trouble if you inflate a lot

Categories

Resources