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
Related
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.
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.
I have 4 views that are controlled by 1 SherlockMapActivity. Currently I am switching between views with the tabs by removeAllViews() and then re-inflate the view again. This seams like a very inefficient way of going about it.
Is there any way to just "hide" a view that has been inflated already and re-position a new view to the front? I have tried every variation of setVisibility, etc, to no avail. Here is how I am going about it right now:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//load our views!
this.baseViewGroup = (ViewGroup)this.findViewById(android.R.id.content);
this.mapView = new MapView(ActivityMain.this, MAP_API_KEY);
this.mapView.setClickable(true);
this.createMenu();
}
#Override
public void onTabSelected(Tab tab, FragmentTransaction ft)
{
Log.v(CLASS_NAME, "tab selected: "+tab.getPosition());
if (0 == tab.getPosition())
{
this.baseViewGroup.removeAllViews();
this.getLayoutInflater().inflate(R.layout.map, this.baseViewGroup);
}
else if (1 == tab.getPosition())
{
this.baseViewGroup.removeAllViews();
this.getLayoutInflater().inflate(R.layout.list, this.baseViewGroup);
}
}
I can then do fancy things with ViewControllers (of sorts) to restart the previous state of the view when it is re-created but this just seams crazy. Is there a better way to do this?
Edit
I have tried saving the views (inflate once, remove but then just re-add) but I get this strange behavior. Basically, all inflated views are shown on top of each other, in a semi-transparent way. No amount of setVisibility() makes them totally go away.
The code I tried (added to onCreate() and onTabSelected() where appropriate):
//in onCreate()
this.mapLayout = this.getLayoutInflater().inflate(R.layout.map, this.baseViewGroup);
this.moreLayout = this.getLayoutInflater().inflate(R.layout.more, this.baseViewGroup);
//in onTabSelected()
ViewGroup content = (ViewGroup)this.mapLayout.getParent();
content.removeAllViews();
content.addView(this.mapLayout);
Donot inflate views again and again. instead, have 4 class level view variables like
private View firstView;
private View secondView;
private View thirdView;
private View fourthView;
now during every tab change/press. remove all child views from parent and add, appropriate view to the parent. like,
parentView.removeAllViews();
parentView.addView(secondView);
Edit:
Pass null for parentView.
instead of this,
this.moreLayout = this.getLayoutInflater().inflate(R.layout.more, this.baseViewGroup);
do this,
this.moreLayout = this.getLayoutInflater().inflate(R.layout.more, null);
I have an Activity with a ViewPager and I have three layouts. In each layout I have 10 buttons each and i would like to make each button do function on a click. So if i go with the trivial method of defining onClick() method for each button, it is going to be funny and tiresome, because I will have 30 of these setOnClickListener(this) calls and 30 onClick Methods.
On going through the Developers site i found a very important piece of function android:onClick, using which i can register all the keys to a custom onClick method in the xml file itself, and then on clicking the button, this method of the Activity will be called.
But In my case, using the View pager i have already brought a view into the screen. I have registered a custom onClick Method called onClickTest() in my Activity.
public void onClickTest(View v) {
int id = v.getId();
String idname = getResources().getResourceEntryName(id);
Log.i("Sen", "clicked view = "+idname);
switch (id) {
case R.id.btn_test:
testActivity();
break;
default:
break;
}
But when i click the button in the viewPager view, i am not able to execute my onClickMethod. I suspect it is not getting the view.
This is the instantiate method of my ViewPager
public Object instantiateItem(View collection, int position) {
final LayoutInflater inflater = (LayoutInflater) TestActivity.this
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
switch (position) {
case 0:
View v0 = inflater.inflate(R.layout.layout1, null, false);
((ViewPager) collection).addView(v0, 0);
return v0;
default:
return true;
}
}
The layout1 has 10 keys and btn_test is one key in it.
Could you please tell me how to make this working?
Thanks,
Sen
Your question is a bit disjoint. You talk about multiple onClick(), then talk about learning about android:onClick, then jump to "registering" an onClick() listener though you don't say what onClickTest() is tied to.
You could choose any one of those methods and get it working. Try adding android:onClick="onClickTest" to your button btn_test in the XML. If you already have, show that next time so we're not left guessing what you've already done :).
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