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
Related
I've seen all the related questions, and tried their answers. I have a ListView inside a fragment and it's onItemClick method is called when inside one activity, but not called when in another one. Everything else is the same. I've tried:
Changing android:clickable explicitly.
Changing android:focusable and android:focusableInTouchMode explicitly.
Calling listView.setItemsCanFocus.
Adding android:descendantFocusability="blocksDescendants" attribute both on fragment and activity root.
Still, it's not working. It's the same fragment with same adapter, which doesn't have conditionals about being in which activity. However, in one activity it works perfectly, and in another, onItemClick is not called. I'm on ICS. Why would this happen?
UPDATE:
Here is the relevant code in my fragment:
dataSource = (ArrayList<Map<String, Object>>) task.getResult();
FeedAdapter adapter = new FeedAdapter(getActivity(), dataSource, getUser());
ListView list = (ListView) rootView.findViewById(R.id.listView);
list.setItemsCanFocus(true);
//just trying these
list.setOnItemClickListener(self);
list.setAdapter(adapter);
adapter.notifyDataSetChanged();
if (dataSource.size() == 0) {
noPostsLabel.setVisibility(View.VISIBLE);
}
And in my adapter:
public View getView(int position, View convertView, ViewGroup parent) {
View view;
final Map<String, Object> post = objects.get(position);
LayoutInflater inflater = LayoutInflater.from(getContext());
if (convertView == null || convertView.getId() == R.id.headerRoot) {
view = inflater.inflate(R.layout.list_item_post_layout, parent, false);
} else {
view = convertView;
}
view.setClickable(false); //just trying these two now, they weren't here originally
view.setFocusable(false);
//populate
view.setTag(post);
[...] //populate the cell. very long code, redacted.
return view;
}
UPDATE 2:
I've also realized some cells are also not selectable in my "working" activity too, when they have a visible HorizontalScrollView within the cell (I have file attaching feature and it's only visible when there are files. Otherwise, it's in GONE visibility state). I have no idea why it's causing such trouble.
UPDATE 3:
I've also found out that views inside the cell are responsive. It's just the cell view itself which is not taking input.
UPDATE 4:
I've ended up moving the tap handler logic to the cell layout itself, instead of relying on list view's handler. I know it's not a good practice but I had to meet a deadline. Besides, it's working pretty smooth now. I'm not closing/answering the question as the technical problem is still present and I haven't found a real solution to it. I've just used a workaround to meet my project deadline.
I am not sure of your problem and I don't see the full code to debug.
I'll submit a sample code which normally should work in a Fragment.
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
ListView list = (ListView) view.findViewById(R.id.listView);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int positi on, long id) {
Log.d(TAG, "onItemClick");
}
});
Notes:
I don't use root or any other cached object. I use the view parameter for calling findViewById().
I don't know self also. Instead I instantiated a new view or AdapterView.
Have you tried setting callbacks for the fragments? That's the way Android recommends it. You can check out - https://developer.android.com/training/basics/fragments/communicating.html#Deliver
I want to have a fragment for each item in a listview, because I want to separate some logic out. I am using a view holder for each item. If the view doesn't exist, I create a new fragment and add it into the container.
holder.mMyFragment = new MyFragment(mActivity, this);
mActivity.getSupportFragmentManager().beginTransaction().add(R.id.my_container, holder.mMyFragment).commit();
Also for each item, I call holder.mMyFragment.setUi(dataSource, position) to set UI of the fragment based on the data source and position.
The problem I'm having is I initialize the UI elements of fragment in onCreateView method of the fragment class, but it's not called when I add the fragment to the item. So later when I call setUi() which uses some UI elements in fragment it complains a NullPointerException. Does anyone have a suggestion? Thanks!
"THERE IS A SOLUTION" for this.
The issue is, you cannot add fragment directly to the container(FrameLayout) with same "id" in listview as you have done in the above code.
The trick is, create listview(Recyclerview) of "LinearLayout". Then dynamically create FrameLayout in adapter and assign different id's for each. Inflate Fragment to FrameLayout and this FrameLayout to LinearLayout.
#Override
protected void onBindBasicItemView(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof VideoHomeViewHolder) {
VideoHomeViewHolder videoHomeViewHolder = (VideoHomeViewHolder) holder;
FrameLayout frameLayout = new FrameLayout(mContext);
frameLayout.setId(position+1); //since id cannot be zero
FragmentHelper.popBackStackAndReplace(mFragmentManager, frameLayout.getId(),
new ShowLatestVideosFragment(mShowLatestVideosItems.get(position)));
videoHomeViewHolder.linearLayout.addView(frameLayout);
}
}
I want to have a fragment for each item in a listview, because I want to separate some logic out.
You can't use fragment as list item views because the API doesn't allow you - View and Fragment aren't even related so there's no way you can use it like that. Make custom views and use adapter getViewTypeCount and getView to use different list item behavior.
Fragment are managed by Activity's FragmentManager or by other Fragments child FragmentManager; while list item views are managed by ListView & ListAdapter. You can use ListViews in Fragments, but not the other way around.
A simple way.
One problem:You should store add restore fragment state.
holder.mMyFragment = new MyFragment(mActivity, this);
int id = View.generateViewId();
findViewByTag("abc").setId(id);
mActivity.getSupportFragmentManager().beginTransaction().add(id, holder.mMyFragment).commit();
Hi I was facing the same problem and I found the way to do it.
My problem was similar to you:
"I want to have a fragment for each item in a listview, because I want to separate some logic out"
In my app I have to give the option to display custom items in vertical (listView) and horizontal (ViewPager) mode. Additionally I had to deal with 18 custom items and each one with different logic, so the best approach was reusing the fragments that I was using for ViewPager in ListView.
I got it but not in the way you were trying, I mean, I used my fragments like "ViewHolders":
Define fragment's widget like variables of class in each fragment.
Create a custom ArrayAdapter and override: getViewTypeCount(), getItemViewType(int position), getCount(), getItem(int position) getView(int position, View convertView, ViewGroup parent)
In getView I checked what kind of layout I needed before "inflate" the respective XML, create a fragment, assign widget from XML to fragment (with rootView.findViewById) and set "tag" with the new fragment.
What you can see at this point is that fragments in ListView never got attached to Activity but you got what you wanted: logic distributed in several parts and all benefits of ListView (reuse of rows, scroll, etc).
If you need I can post part of my code but you have to deal with "spanglish" ;).
UPDATED
All the problem is because when you create a Fragment to be used with ViewPager, usually all "layout and "setup" code is inside onCreateView method, I mean:
Get the view object you are going to use (View rootView = inflater.inflate(R.layout.fragment_question_horizontal_container, container, false);)
Get the widgets from above layout, define behaviors, fonts, etc: (answer = (EditText)rootView.findViewById(R.id.answer_question_text);)
Until this point there is nothing weird.
If you are going to use a fragment with the behavior described above you have to "emulate" the call to onCreateView, fill the data and attach it to the listView.
Here is the trick: split the code in onCreateView in some methods that doesn't care about who's calling them. An example of my onCreateView:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_pregunta_horizontal_container, container, false);
addAnswerLayout(rootView, R.layout.fragment_pregunta_texto, getActivity());
setUpComponents(rootView);
//those 2 methods are about my app logic so I'm not going to talk much about them
setUpQuestionState(savedInstanceState);
readSavedAnswer();
return rootView;
}
public void addAnswerLayout(View rootView, int optionId, Context context) {
mContext = context;
RelativeLayout relativeLayout = (RelativeLayout)rootView.findViewById(R.id.pregunta_container);
LayoutInflater inflater = ((Activity)mContext).getLayoutInflater();
View newView = inflater.inflate(optionId, relativeLayout, false);
relativeLayout.addView(newView);
}
public void setUpComponents(View rootView) {
//next line: some heritage that you can ignore
super.setUpComponents(rootView);
respuesta = (EditText)rootView.findViewById(R.id.pregunta_respuesta_texto);
respuesta.setTypeface(UiHelper.getInstance(getActivity()).typeface);
respuesta.setTextColor(mContext.getResources().getColor(R.color.drs_gris));
...
}
Now let's go to the CustomArrayAdapter for list view:
Define your customArrayAdapter something like this: PreguntasVerticalArrayAdapter extends ArrayAdapter<Pregunta> where "Pregunta" is a generic Fragment with the logic from above.
Override getViewTypeCount(), getItemViewType(int position), getCount(), getItem(int position) getView(int position, View convertView, ViewGroup parent).
The getView follow the same behavior: get the object for the given position in params, reuse a "viewholder" and fill the data. Here my getView:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView = convertView;
Pregunta pregunta = mData.get(position);
if (rowView == null)
rowView = createQuestionUI(pregunta, parent, position);
fillDataInRow((PreguntaUI)rowView.getTag(), pregunta, position);
return rowView;
}
private View createPreguntaUI(Pregunta pregunta, ViewGroup parent, int position) {
View rootView = null;
LayoutInflater inflater = (mPreguntasVerticalFragment.getActivity()).getLayoutInflater();
//you can ignore this part of the code ralted to Bundle.
Bundle args = new Bundle();
args.putLong(PreguntaUI.PREGUNTAUI_ID, pregunta.getIdpregunta());
args.putInt(PreguntaUI.PREGUNTAUI_INDEX, position);
args.putInt(PreguntaUI.PREGUNTAUI_TOTAL_QUESTIONS, getCount());
//internal method of "pregunta" to know what kind of question it is.
String tipo = pregunta.getTipo();
if (tipo.equalsIgnoreCase(PreguntaType.TEXT.toString())) {
rootView = inflater.inflate(R.layout.fragment_pregunta_vertical_container, parent, false);
Pregunta_texto pregunta_texto = new Pregunta_texto();
pregunta_texto.setArguments(args);
//LOOK AT THIS POINT!!!: I'm calling the same methods that I called in onCreateView fragment's method.
pregunta_texto.addAnswerLayout(rootView, R.layout.fragment_pregunta_texto,
mPreguntasVerticalFragment.getActivity());
pregunta_texto.setUpComponents(rootView);
pregunta_texto.setUpQuestionState(null);
pregunta_texto.readSavedAnswer();
//I'm adding the fragment to reuse it when I can
rootView.setTag(pregunta_texto);
}
else if ...
return rootView;
}
That is all... at this point, if you have enough experience dealing with CustomArrayAdapters and Fragments you probably got it! :D
From the Android Documentation : "A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities."
For your activity, do you want to add 2 fragments where the first one displays a listView (= ListFragment and the other one is in the right and is shown only when the user clicks on an item (from the first fragment or listView) ?
Instead of using ListFragment, you can use RecyclerView, Android has documentation on that:
https://developer.android.com/training/basics/fragments/creating.html#AddInLayout
I am coding an application that has an activity that can supply the end user with data in two formats a bar graph view using the Teechart api (available here: http://www.steema.com/teechart/mobile) and a listview native in android. Currently I have some logic like so
first i Initialize a boolean flag = true;
I then use this button logic to change between views.
OnClickListener changeViewListener = new OnClickListener(){
public void onClick(View v){
if(!flag){
listLayout.setVisibility(View.GONE);
chartView.setVisibility(View.VISIBLE);
changeView.setText("List");
flag = true;
}else{
listLayout.setVisibility(View.VISIBLE);
chartView.setVisibility(View.GONE);
changeView.setText("Graph");
flag = false;
}
}
};
This code works great and gives me no trouble, I am just questioning whether this can be done a better way such as using a view flipper? And if so how do I implement the view flipper code to switch between these two views?
Or should I be using fragments for each view? Any help would be much appreciated.
Maybe this could be of help to you:
Animate between Views
It gives a generic example, may be you can tweak it to get the flip effect you want.
Update:
That tutorial also gives links to various Animation docs. From that, I think you can use Rotate Animation to create the flip effect. You can give the angle of rotation and the pivot about which to rotate the view.
The concept is that you rotate one view out and rotate in the other view.
Update:
View Flipper is an implementation of Animating between views. The above method I posted was generic, you can toy around with values and create animations with you having much more finer control. You can create transitions between Views that others may never have tried.
Steps for View Flipper:
1. In View Flipper, you define a Flipper element in your Layout XML file. To this flipper element, you add two child elements, which could simply be two Views OR two Layouts OR one View and one Layout. The View Flipper flips between these two Views you have defined.
2. Once you have created this much in XML, you then create four animation rules under /res/anim for the following types of entry and exit transitions:
a. Left In
b. Left Out
c. Right In
d. Right Out
3. After 1 and 2, you now add Touch or Gesture listeners in your code, to listen for Touch and Listen events. Inside these listeners, you then initiate the animation using vf.setInAnimation() or vf.setOutAnimation(), where vf is your ViewFlipper instance.
You can find complete code over here:
1. View Flipper Tutorial 1
2. View Flipper Tutorial 2
Update:
A few tweaks have to be made to make View Flipper work with ListView. I found this other SO question where the same problem was solved with a minor edit. Check it out here.
If you only have two views that you want to switch between, this approach is good enough. However, you can use a view pager to implement this Such an approach would particularly be useful if you have several views so that you don't render them all at once and waste memory. ViewPager will manage when to create and destroy views.
Define in layout
<android.support.v4.view.ViewPager
android:id="#+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Create an adapter
class MyPagerAdapter extends PagerAdapter {
public int getCount() {
return 2;
}
public Object instantiateItem(View collection, int position) {
LayoutInflater inflater = (LayoutInflater) collection.getContext().getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
// Inflate the correct view based on position
View view = inflater.inflate(R.layout.yourLayoutHere, null);
// ... Setup the view
((ViewPager) collection).addView(view, 0);
return view;
}
#Override
public void destroyItem(View arg0, int arg1, Object arg2) {
((ViewPager) arg0).removeView((View) arg2);
}
#Override
public boolean isViewFromObject(View arg0, Object arg1) {
return arg0 == ((View) arg1);
}
#Override
public Parcelable saveState() {
return null;
}
}
Set the adapter
ViewPager viewPager = findViewById(R.id.viewPager);
viewPager.setAdapter(new MyPagerAdapter());
Now on button click, you can set the current item of view pager.
viewPager.setCurrentItem(position);
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 need to create ViewPager in Android with 5 slides, each consists of image and text. I have an array with resources for images:
private static final int[] images = {R.drawable.tutorial_step_01, R.drawable.tutorial_step_02, R.drawable.tutorial_step_03, R.drawable.tutorial_step_04, R.drawable.tutorial_step_05, R.drawable.tutorial_step_06};
then I create adapter:
#Override
public Object instantiateItem(ViewGroup container, int position) {
LinearLayout tv = (LinearLayout) inflater.inflate(R.layout.tut_slide, null);
TextView title = (TextView) tv.findViewById(R.id.tut_title);
title.setText(getResources().getText(titles[position]));
TextView content = (TextView) tv.findViewById(R.id.tut_content);
ImageView image = (ImageView) tv.findViewById(R.id.tut_image);
slide_image = BitmapFactory.decodeResource(getResources(), images[position]);
image.setImageBitmap(slide_image);
content.setText(getResources().getText(contents[position]));
((ViewPager) container).addView(tv, 0);
return tv;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((LinearLayout) object);
//
}
trouble is that fact android don't want to collect image after I choose another page. So, after 10-15 changes it goes out with OutOfMemory exception. Then I added to initializung rows
if (slide_image!= null) {
slide_image.recycle();
System.gc();
}
And it's work good! But except one thing: I have black screen instead of first image, whcih is replaced by real one after few flips. So I don't know what to do with such memory leaking
Well, I solved the problem finally. I faced it with a very similar case and as I've seen so many questions related to the same problem, I chose this question as it's yet not answered.
The PagerAdapter should call the destroyItem method not only when it surpasses the offLimitScreenPageLimit but also when a screen rotation occurs, but it doesn't, so it has to be forced to do so... to achieve it, you just have to set to null the adapter on the onStop or onDestroy method of the activity.
#Override protected void onDestroy(){
pager.setAdapter(null);
}
Cheers!
It's not clear what you are using but I encountered a similar problem.
I'm assuming you are using FragmentPagerAdapter.
When you scroll away using that adapter, it does not destroy the pages out of view and out of cache. If there is an ImageView in a fragment used by FragmentPageAdapter, OOM is inevitable
Just change the extend of the adapter to
FragmentStatePagerAdapter
This will destroy the fragments not in use and leave more memory free for new fragments.
It's still not perfect, I have found that sometimes I can scroll faster than the garbage collector picks up the destroyed bitmaps, but its pretty damn close.
If I was looking to improve it, I would override destroyItem, and then get the bitmap in use from the imageview and .recycle the bitmap.
Recycle ImageView's Bitmap
That behaviour shouldn't be related with a memory leak. It looks like it's related to when and how you recycle bitmaps within your viewpager updating lifecycle. Try calling onPageSelected() or notifyDatasetChanged() manually at some point on your initialization.
This solutions might not solve the problem completely, but give it a try. It's hard to tell with your explanation.
In My case I have 31 page in ViewPager. I use this :
#Override
public void destroyItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
if(dbCon!=null)
dbCon.close();
ViewPager viewPager = (ViewPager)container;
View view = (View) object;
viewPager.removeView(view);
}
and everthing works fine. Alhamdulillah.