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);
Related
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
MyActivity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//list is global var
list = (MyListFragment) getFragmentManager().findFragmentById(com.myapp.R.id.mainActivity_myListFragment);
//add all objects to list
list.getListView().invalidateViews();
}
#Override
protected void onResume(){
super.onResume();
list.getListView().invalidateViews();
}
MyListFragment
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
MyAdapter adapter = new MyAdapter(new ArrayList());
setListAdapter(adapter);
}
public View getView(int position, View convertView, ViewGroup container) {
View view = super.getView(position, convertView, container);
TextView main = (TextView) view.findViewById(mylibs.common.R.list_main);
TextView sub = (TextView) view.findViewById(mylibs.common.R.list_sub);
ImageView image = (ImageView) view.findViewById(mylibs.common.R.list_image);
//set the View content here.
}
The problem is, I can not get the first run to display correctly. However, if onResume() is called by Android, it all displays perfectly fine. How do I solve this?
The first item in the list displays correctly at all times, only the rest of the list is problematic.
When debugging getView() right after onCreate(), it clearly shows that the TextView and ImageView are set by the data that I want in the getView() method, but it does not display and instead of showing the data that I want, it shows the toString() String of the Object that's in the Adapter for one of the TextView and all others are left blank.
You are doing some things here which are most likely the root of your problem:
A) Fragments are not inflated during your Activity's onCreate() function, so you cannot access them there. The best practice here is to access / manipulate fragment views during the Fragment's own onCreateView() lifecycle callback. This is the point right before they are inflated onto the screen, so the perfect spot. The activity and fragments you create should be relatively independent of each-other. Fragments should be re-usable. For example, what would you do if you needed this fragment in X more activities?
B) Your getView() is suspicious to me. List views are usually defined by the listview view group you have in xml, the adapter (instantiated by onCreateView()), your model collection of objects which the list view will contain, and a separate xml with views which define each cell in the list. getView() is normally a function you override within your adapter (using the view holder pattern), where you access and populate the views that make up a cell from your separate xml.
Maybe you have it set up correctly but what you've given us doesn't read like it (the adapter definitely shouldn't be set in the fragment's onCreate() anyway).
Vogella has a nice tutorial on list views. Good luck!
Android is a wreck and this is a waste of time.
I am still looking for the answer, but a workaround is to create a new Thread, sleep for 1ms, then runonUIThread() invalidateViews().
This might be a little bit hard to explain, so the best way I can think of, is providing you a Video showing up the issue.
In the Video I show myself scrolling listview, and after 5 seconds, a View is created and added inside that holder in the bottom. In that moment, listview is refreshed.
http://tinypic.com/player.php?v=vpz0k8%3E&s=8#.U0VrIvl_t8E
The issue is the following:
I've an Activity with a layout that consists of a:
Fragment (above RelativeLayout), match parent, match parent.
RelativeLayout, as wrap content.
The fragment displays a ListView with animations for every row.
If I add a View on the "RelativeLayout", it makes the fragment to readjust to the new size, as it's set above this RelativeLayout, so every Row is rebuilt again.
Do you guys think in any way to avoid this?
EDIT: Sourcecode:
https://bitbucket.org/sergicast/listview-animated-buggy
Don't start the animation if the layout process for the added footer view is running. The end of the layout process can be determined using the ViewTreeObserver (the start obviously starts with adding the footer view):
hand.postDelayed(new Runnable() {
#Override
public void run() {
ViewTreeObserver viewTreeObserver = holder.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
holder.getViewTreeObserver().removeGlobalOnLayoutListener(this);
mIgnoreAnimation = false;
}
});
mIgnoreAnimation = true;
holder.addView(viewToAdd);
}
}, 5000);
Add this method to your Activity:
public boolean ignoreAnimation() {
return mIgnoreAnimation;
}
And check it in your Fragment:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Context context = FragmentTest.this.getActivity();
TextView tv = new TextView(context);
tv.setText("Pos: " + position);
tv.setTextSize(35f);
if (runAnimation()) {
Animation anim = AnimationUtils.loadAnimation(context, R.anim.animation);
tv.startAnimation(anim);
}
return tv;
}
private boolean runAnimation() {
Activity activity = getActivity();
if (activity != null && activity instanceof MainActivity) {
return ! ((MainActivity)activity).ignoreAnimation();
}
return true;
}
Of course the whole Activity - Fragment communication can be improved considerably but the example gives you the idea how to solve the problem in general.
While it prevents the animation from being started, it doesn't prevent the ListView from being refreshed although the user won't notice. If you are concerned about performance you can improve the Adapter code by re-using the views:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Context context = FragmentTest.this.getActivity();
TextView tv = null;
if (convertView != null && convertView instanceof TextView) {
tv = (TextView) convertView;
}
else {
tv = new TextView(context);
}
Yes, I can think of a possible way to solve this.
Your problem is:
You have set layout params of your holder to wrap_content. By default, when it has no content, it is "zero-sized" somewhere in the bottom and invisible to you (not invisible in terms of Android, though, sic!)
When you add a View to this holder, the framework understands, that the size of your holder container is different now. But this container is a child of another container - your root RelativeLayout, which, in turn, contains another child - your <fragment>.
Thus, framework decides, the root container alongside with its children should get laid out again. That's why your list gets invalidated and redrawn.
To fix the issue with list getting invalidated and redrawn, simply specify some fixed layout parameters to your holder. For example:
<RelativeLayout
android:id="#+id/holder"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" >
</RelativeLayout>
That will prevent the list from being redrawn. But in that case you'll get your holder displayed from the very beginning.
Yes. This is the expected behavior of RelativeLayout
You are adding the ListView Fragment and TextView into a RelativeLayout, So whenever there is a change in the child view dimension, will affect the other child in the RelativeLayout.
So here when you add a new TexView , the other child Fragment is affected even though its height is match_parent.
You can fix this only by changing the parent layout to LinearLayout.
I need to have an scroll with items together, and the selected item should expand a part down.
I am currently using a Gallery (I tried with viewflow and viewpager, but the items have much space between them), but I need to know how can I do this effect.
I have 2 ideas, but i don't know how can I implement it.
1) The expandable part is a LinearLayout with visibility=gone, and when the item is selected, this layout should be visible. (Gallery do not have "onItemSelectedListener")
2) Treat each element as a fragment (once I use a Viewpager that use this, https://github.com/mrleolink/SimpleInfiniteCarousel)
It does not necessarily have to be a gallery, any idea is welcome
I am working on an Activity.
Depends on the behavior that you want. Some questions can more than one item be expanded at a time? Do you want the views to be paged (snap into place) or smooth scroll them?
One Suggestion I have is to make a custom view for the individual cells. Then add them programmatically to a HorizontalScrollView Object.
HorizontalScrollView hsv = new HorizontalScrollView(activity);
LinearLayout hll = new LinearLayout(activity);
hll.setOrientation(LinearLayout.HORIZONTAL);
for(int i=0;i<items.length();i++){
hsv.addView(new CustomExpandView(item));
}
The CustomExpandView would be used for your cells and could be something like this...
public class CustomExpandView extends RelativeLayout implements OnClickListener {
MyActivity mActivity = null;
ImageView ivImage, ivOverImage;
RelativeLayout rlView;
public CustomExpandView(Context context) {
super(context);
initialize();
}
public CustomExpandView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public void initialize() {
mActivity = (MyActivity) this.getContext();
LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.custom_cell_expand, this, true);
//you can initialize subviews here
rlView = (RelativeLayout) getChildAt(0);
ivImage = (ImageView) rlView.getChildAt(0);
ivOverImage = (ImageView) rlView.getChildAt(1);
rlView.setOnFocusChangeListener(new OnFocusChangeListener(){
#Override
public void onFocusChange(View v, boolean hasFocus) {
LinearLayout expand = v.findViewById(R.id.view_i_want_to_expand);
if(hasFocus)
expand.setVisibility(View.VISIBLE);
else
expand.setVisibility(View.GONE);
}
});
}
You gave the answer yourself. You can use a ViewPager, with fragments, and have an animation to extend the lower part of the window. Depends on whether you want the windows to be full screen or not. A viewpager doesn't necessarily need fragments, you can use ordinary views, and an appropriate adapter. Just play with it and see which solution you like most.
Next time, just create the code and the app, and ask a much more specific question, with code to illustrate the issue you're experiencing.
You could simply define a TableView with just one TableRow (or as many as you need) and set a onClickListener for each of those Views inside the TableRow, which would make that on any click, the selected View would expand itself.
I don't know whether you'll have a static number of Views inside that row or you'll construct them dynamically, but this should work for any of them, the real "work" here about populating that row.
Once you have your row of Views, simply declare an onClickListener() on each of them. For example, this should be enough:
OnClickListener myListener = new OnClickListener() {
#Override
public void onClick(final View v) {
v.setVisibility(View.VISIBLE);
}
};
And as the onClick event for all of your items inside the TableRow:
for (View v : myTableRowViews)
v.setOnClickListener(myListener);
This has a disadvantage: You can know which View has been clicked for selection, but natively you cannot know which has been deselected, so you'll need to keep track of the last selected tab declaring a class-wide variable and setting it each time onClick() is fired, so your listener will become something like this:
// In your class declare a variable like this
View lastSelected = null;
OnClickListener myListener = new OnClickListener() {
#Override
public void onClick(final View v) {
if (lastSelected != null)
lastSelected.setVisibility(View.GONE);
v.setVisibility(View.VISIBLE);
lastSelected = v;
}
};
Additionally, you can set an animation to the effect to make it more attractive, but mainly this is the idea.
One last thing: To make it work this way you'll need to set the layout_height of both your TableRow and the View items inside, so it may expand afterwards when you set the additional part as visible. Also, to make it look good all of your Views will have to be the same height (both in the reduced and extended state).
We're suffering from a very strange issue with ViewPager here. We embed lists on each ViewPager page, and trigger notifyDataSetChanged both on the list adapter and the view pager adapter when updating list data.
What we observe is that sometimes, the page does not update its view tree, i.e. remains blank, or sometimes even disappears when paging to it. When paging back and forth a few times, the content will suddenly reappear. It seems as if Android is missing a view update here. I also noticed that when debugging with hierarchy viewer, selecting a view will always make it reappear, apparently because hierarchy viewer forces the selected view to redraw itself.
I could not make this work programmatically though; invalidating the list view, or the entire view pager even, had no effect.
This is with the compatibility-v4_r7 library. I also tried to use the latest revision, since it claims to fix many issues related to view pager, but it made matters even worse (for instance, gestures were broken so that it wouldn't let me page through all pages anymore sometimes.)
Is anyone else running into these issues, too, or do you have an idea of what could be causing this?
If the ViewPager is set inside a Fragment with a FragmentPagerAdapter, use getChildFragmentManager() instead of getSupportFragmentManager() as the parameter to initialize your FragmentPagerAdapter.
mAdapter = new MyFragmentPagerAdapter(getChildFragmentManager());
Instead of
mAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
We finally managed to find a solution. Apparently our implementation suffered of two issues:
our adapter did not remove the view in destroyItem().
we were caching views so that we'd have to inflate our layout just once, and, since we were not removing the view in destroyItem(), we were not adding it in instantiateItem() but just returning the cached view corresponding to the current position.
I haven't looked too deeply in the source code of the ViewPager - and it's not exactly explicit that you have to do that - but the docs says :
destroyItem()Remove a page for the given position. The adapter is responsible for removing the view from its container, although it only must ensure this is done by the time it returns from finishUpdate(ViewGroup).
and:
A very simple PagerAdapter may choose to use the page Views themselves as key objects, returning them from instantiateItem(ViewGroup, int) after creation and adding them to the parent ViewGroup. A matching destroyItem(ViewGroup, int, Object) implementation would remove the View from the parent ViewGroup and isViewFromObject(View, Object) could be implemented as return view == object;.
So my conclusion is that ViewPager relies on its underlying adapter to explicitly add/remove its children in instantiateItem()/destroyItem(). That is, if your adapter is a subclass of PagerAdapter, your subclass must implement this logic.
Side note: be aware of this if you use lists inside ViewPager.
I had the exact same problem but I actually destroyed the view in destroyItem (I thought). The problem however was that I destroyed it using viewPager.removeViewAt(index); insted of viewPager.removeView((View) object);
Wrong:
#Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
viewPager.removeViewAt(position);
}
Right:
#Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
viewPager.removeView((View) object);
}
ViewPager tries to do clever stuff around re-using items, but it requires you to return new item positions when things have changed. Try adding this to your PagerAdapter:
public int getItemPosition (Object object) { return POSITION_NONE; }
It basically tells ViewPager that everything has changed (and forces it to re-instantiate everything). That's the only thing I can think of off the top of my head.
Tried too many solutions but unexpectedly viewPager.post() worked
mAdapter = new NewsVPAdapter(getContext(), articles);
viewPager.post(new Runnable() {
#Override
public void run() {
viewPager.setAdapter(mAdapter);
}
});
The Android Support Library has a demo Activity that includes a ViewPager with a ListView on every page. You should probably have a look and see what it does.
In Eclipse (with Android Dev Tools r20):
Select New > Android Sample Project
Select your target API level (I suggest the newest available)
Select Support4Demos
Right-click the project and select Android Tools > Add Support Library
Run the app and select Fragment and then Pager
The code for this is in src/com.example.android.supportv4.app/FragmentPagerSupport.java. Good luck!
I ran into this and had very similar issues. I even asked it on stack overflow.
For me, in the parent of the parent of my view someone subclassed LinearLayout and overrode requestLayout() without calling super.requestLayout(). This prevented onMeasure and onLayout from being called on my ViewPager (although hierarchyviewer manually calls these). Without being measured they'll show up as blank in ViewPager.
So check your containing views. Make sure they subclass from View and don't blindly override requestLayout or anything similar.
Had the same issue, which is something to do with ListView (because my empty view shows up fine if the list is empty). I just called requestLayout() on the problematic ListView. Now it draws fine!
I ran into this same problem when using a ViewPager and FragmentStatePagerAdapter. I tried using a handler with a 3 second delay to call invalidate() and requestLayout() but it didn't work. What did work was resetting the viewPager's background color as follows:
MyFragment.java
private Handler mHandler;
private Runnable mBugUpdater;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = new ViewPager(getActivity());
//...Create your adapter and set it here...
mHandler = new Handler();
mBugUpdater = new Runnable(){
#Override
public void run() {
mVp.setBackgroundColor(mItem.getBackgroundColor());
mHandler = null;
mBugUpdater = null;
}
};
mHandler.postDelayed(mBugUpdater,50);
return rootView;
}
#Override
public void onPause() {
if(mHandler != null){
//Remove the callback if it hasn't triggered yet
mHandler.removeCallbacks(mBugUpdater);
mHandler = null;
mBugUpdater = null;
}
super.onPause();
}
I had a problem with the same symptoms, but a different cause that turned out to be a silly mistake on my part. Thought I'd add it here in case it helps anyone.
I had a ViewPager using FragmentStatePagerAdapter which used to have two fragments, but I later added a third. However, I forgot that the default off screen page limit is 1 -- so, when I'd switch to the new third fragment, the first one would get destroyed, then recreated after switching back. The problem was that my activity was in charge of notifying these fragments to initialize their UI state. This happened to work when the activity and fragment lifecycles were the same, but to fix it I had to change the fragments to initialize their own UI during their startup lifecycle. In the end I also wound up changing setOffscreenPageLimit to 2 so that all three fragments were kept alive at all times (safe in this case since they were not very memory intensive).
I had similar issue. I cache views because I need only 3 views in ViewPager. When I slide forward everything is okay but when I start to slide backward occurs error, it says that "my view already has a parent". The solution is to delete unneeded items manually.
#Override
public Object instantiateItem(ViewGroup container, int position) {
int localPos = position % SIZE;
TouchImageView view;
if (touchImageViews[localPos] != null) {
view = touchImageViews[localPos];
} else {
view = new TouchImageView(container.getContext());
view.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
touchImageViews[localPos] = view;
}
view.setImageDrawable(mDataModel.getPhoto(position));
Log.i(IRViewPagerAdpt.class.toString(), "Add view " + view.toString() + " at pos: " + position + " " + localPos);
if (view.getParent() == null) {
((ViewPager) container).addView(view);
}
return view;
}
#Override
public void destroyItem(ViewGroup container, int position, Object view) {
// ((ViewPager) container).removeView((View) view);
Log.i(IRViewPagerAdpt.class.toString(), "remove view " + view.toString() + " at pos: " + position);
}
..................
private static final int SIZE = 3;
private TouchImageView[] touchImageViews = new TouchImageView[SIZE];
For me the problem was coming back to the activity after the app process was killed. I am using a custom view pager adapter modified from the Android sources.The view pager is embedded directly in the activity.
Calling viewPager.setCurrentItem(position, true);
(with animation) after setting the data and notifyDataSetChanged() seems to work, but if the parameter is set to false it doesn't and the fragment is blank. This is an edge case which may be of help to someone.
For Kotlin users:
In your fragments;
Use childFragmentManager instead of viewPagerAdapter