In my app, there is currently a section that holds a fragment that queries the database for a list of objects and then displays them (using a ListView).
When a user clicks one of the objects in the list, the section grows bigger in height to incorporate a different fragment that .replace()s the ListView fragment and moves up.
This new fragment displays information related to the object the user clicked.
One of the views in this fragment is a ViewPager that contains only 2 fragments (at most):
A timer fragment.
A calendar fragment.
The timer fragment is a fragment that simply uses a CountDownTimer and changes a TextView's text onTick(). This fragment may or may not be shown, depending on the object.
The calendar fragment however, contains an heavier view - a custom calendar view.
The calendar is supposed to show the days a year before and after the current date (now).
The calendar fragment (or view, actually, since there is no logic at all in the fragment) causes the animation that fires when a user clicks on an object to stutter and it looks bad. This only happens in the first time an object is clicked. In the second time it's smoother (I guess it's thanks to the ViewPager saving the fragment's state, but I'm not sure...).
I was wondering whether there is any specific bottleneck/problem in the code that would cause it to behave like that. My best guess is that there are just too many views being loaded together.
The first solution I thought of, is to basically move the animation inside the fragment and fire it only after the fragment is fully loaded and ready to be displayed. That, however, would mean that I will be controlling the parent (the container of the fragment) from within the fragment itself... not sure whether this is a good design or not. I could create a listener for that, and place its call in the end of onCreateView(), though.
Another possible solution, and this is just a theory of mine, but creating all of the views on a separate thread, and only then adding them to the UI in the main thread could maybe slightly speed up the process. Although, I'm not really sure how better that would be (in terms of performance), if at all, and how much of a good practice this is.
How can I optimize my CalendarView (or maybe my whole "Object View Fragment") in order to allow the animation to work properly?
CalendarView consists of:
A vertical LinearLayout that contains 2 sub-views:
A topbar which is a TableLayout with a single with the names of the 7 days.
A GridView which is filled with TextViews of day numbers.
Some code:
MainActivity - replaces the current fragment
#Override
public void OnGoalSelected(Parcelable goal) {
Log.i(TAG, "GoalSelected");
isMinimized = false;
HabitGoalViewFragment newFragment = new HabitGoalViewFragment();
Bundle args = new Bundle();
args.putParcelable(GOAL_POSITION_KEY, goal);
newFragment.setArguments(args);
getSupportFragmentManager().addOnBackStackChangedListener(this);
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.fadein, R.anim.fadeout, R.anim.fadein, R.anim.fadeout)
.replace(R.id.cardFragmentContainer, newFragment)
.addToBackStack(null)
.commit();
mActionBar.setCustomView(R.layout.ab_goal_view);
mLL.getLayoutParams().height += screenHeight;
ObjectAnimator objAnim = ObjectAnimator.ofFloat(mLL, "translationY", AmountToMove).setDuration(500);
objAnim.setInterpolator(new DecelerateInterpolator());
objAnim.start();
}
HabitGoalView
public static Class<?>[] mFragmentArray = {
HabitCalendarFragment.class,
HabitDurationTimerFragment.class
};
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_goal_view, container, false);
mActionBar = getActivity().getActionBar();
mActionBar.getCustomView().findViewById(R.id.goal_view_back_button_parent).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getActivity().getSupportFragmentManager().popBackStackImmediate();
}
});
mHabitGoal = getArguments().getParcelable(MainActivity.GOAL_POSITION_KEY);
............
mViewPager = (ViewPager) view.findViewById(R.id.pager);
mViewPager.setAdapter(new ViewPagerAdapter(getActivity().getSupportFragmentManager(), getActivity()));
return view;
}
private class ViewPagerAdapter extends FragmentStatePagerAdapter {
private Context mContext;
public ViewPagerAdapter(FragmentManager fm, Context context) {
super(fm);
mContext = context;
}
#Override
public Fragment getItem(int position) {
return Fragment.instantiate(mContext, mFragmentArray[position].getName());
}
#Override
public int getCount() {
return mFragmentArray.length;
}
}
CalendarView (This is the View itself, not the fragment!)
public class CalendarView extends LinearLayout {
private final String TAG = this.getClass().getName();
private Context mContext;
private TableLayout mTopBarTableLayout;
private TableRow mTableRow;
public CalendarView(Context context) {
super(context);
mContext = context;
this.setOrientation(VERTICAL);
mTopBarTableLayout = new TableLayout(mContext);
mTopBarLinearLayout.setStretchAllColumns(true);
mTableRow = new TableRow(mContext);
int[] mDaysList = {R.string.days_sunday, R.string.days_monday, R.string.days_tuesday,
R.string.days_wednesday, R.string.days_thursday, R.string.days_friday, R.string.days_saturday}; //
AutoScrollingTextView mDayTextView;
int padding;
for (int i = 0; i < 7; i++) {
mDayTextView= new AutoScrollingTextView(mContext);
padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics());
mDayTextView.setPadding(padding, padding, padding, padding);
mDayTextView.setTextSize(16);
mDayTextView.setGravity(Gravity.CENTER);
mDayTextView.setText(getResources().getString(mDaysList[j]).substring(0, 3).toUpperCase(Locale.US));
mDayTextView.setWidth(0);
mDayTextView.setSingleLine(true);
mDayTextView.setHorizontallyScrolling(true);
mDayTextView.setEllipsize(TextUtils.TruncateAt.MARQUEE);
mTableRow.addView(mDayTextView, i);
}
mTopBarLinearLayout.addView(mTableRow, 0);
this.addView(mTopBarLinearLayout, 0);
this.addView(new CalendarGridView(mContext), 1);
}
private class CalendarGridView extends GridView {
Context mContext;
DateTime CurrentMonthDateTime, NextYearDT, LastYearDT;
int offset;
public CalendarGridView(Context context) {
super(context);
mContext = context;
init();
}
public CalendarGridView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
public CalendarGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mContext = context;
init();
}
public void init() {
this.setNumColumns(7); // 7 columns - 1 for each day
this.setStretchMode(STRETCH_COLUMN_WIDTH);
this.setAdapter(new CalendarAdapter());
CurrentMonthDateTime = DateTime.now();
LastYearDT = DateTime.now().minusYears(1).withDayOfMonth(1);
offset = LastYearDT.getDayOfWeek();
if (offset == 7)
offset = 0;
int n = Days.daysBetween(LastYearDT.withTimeAtStartOfDay(), CurrentMonthDateTime.withTimeAtStartOfDay()).getDays() + offset;
Log.i(TAG, "Days Offset = " + n);
this.setSelection(n);
}
private class CalendarAdapter extends BaseAdapter {
private int offset, n;
private DateTime mDateTime, mDateToPrint;
public CalendarAdapter() {
mDateTime = DateTime.now().minusYears(1).withDayOfMonth(1);
NextYearDT = DateTime.now().plusYears(1).withDayOfMonth(1);
n = Days.daysBetween(mDateTime.withTimeAtStartOfDay(),
NextYearDT.withTimeAtStartOfDay()).getDays();
// round up to the nearest number divisible by 7
n += (7 - n%7);
offset = mDateTime.getDayOfWeek(); // 1 - mon, 2 - tue ... 7 - sun
// set first day to Sunday
if (offset == 7)
offset = 0;
mDateTime = mDateTime.minusDays(offset);
}
#Override
public int getCount() {
return n;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
SquareTextView view = (SquareTextView) convertView;
if (convertView == null) {
view = new SquareTextView(mContext);
view.setTextSize(18);
view.setGravity(Gravity.CENTER);
view.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
Log.i(TAG, ((SquareTextView) v).getText().toString());
}
});
} else {
view.setBackgroundResource(0); // TODO set as drawable and then remove it
view.setTag(null);
}
mDateToPrint = mDateTime.plusDays(position);
if (mDateToPrint.getDayOfMonth() == 1)
view.setTag(mDateToPrint.monthOfYear().getAsShortText(Locale.ENGLISH));
view.setText(mDateToPrint.getDayOfMonth() + "");
if (mDateToPrint.withTimeAtStartOfDay().isEqual(CurrentMonthDateTime.withTimeAtStartOfDay())) {
//view.setBackgroundResource(R.color.background_gray);
view.setBackgroundColor(getResources().getColor(R.color.green));
view.setTag("today");
}
return view;
}
}
}
}
So I finally found a solution! At the end the source to my problem was elsewhere, but just in case someone would have the same kind of problem/stuttering, I will share the best way to solve it based on my knowledge - using listeners. It was also recommended in the Android development tutorials.
Using a custom listener interface you can let the containing activity know that the fragment has finished all of it's loading. That way, you can make the animation run only after the fragment has been loaded, which would result in a smooth animation.
Example interface:
public interface OnFragmentLoadedListener {
public abstract void OnFragmentLoaded(Object A);
}
You then implement the interface in the activity that contains the fragment:
public class MainActivity extends Activity implements MyFragment.OnFragmentLoadedListener {
................
public void OnFragmentLoaded(Object A) {
//Do something...
}
Then you need to set the listener inside the fragment. You can either create a method like setOnFragmentListener(OnFragmentListener mListener) or, as I prefer, get the reference to the listener in the onAttach() method of the Fragment class:
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
this.mListener = (OnFragmentLoadedListener) activity;
} catch (final ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnFragmentLoadedListener");
}
}
Finally, you need to let the listener know when the fragment is ready to be shown.
In my case, that would be after ANOTHER fragment would load, so I put my listener call in the interface implementation of the containing (and contained) fragment.
if(mListener != null)
mListener.OnFragmentLoaded(A);
My source of my problem was really... JODA TIME
The source to my problem was actually my usage of the joda-time library.
I used the original joda-time for java, which was a mistake.
Everything worked fine and all but after trying hard to solve my problem I wondered whether it was possible that joda-time was actually the problem since the rest of my calendar code was very much like the calendar widget provided with android.
Surprisingly, I came across this question/answer, which proved my theory right.
At first, I was actually quite disappointed, because I thought I'd have to go back to using androids' default Calendar class, and then I remembered there was a version of joda for android, and thought there was nothing to lose by trying it out.
Anyways, turns out this android version really did wonders. It worked like magic and sped up my whole app! You can find this great library here: joda-time-android
I don't really know why, which is why I invite anyone who knows to explain it in a new answer, which I'll gladly mark as accepted.
Related
I'm new to Android programming and trying to figure out how to optimize my ListView adapter. I wrote a custom adapter to add CardViews to my ListView:
public class CardAdapter extends BaseAdapter {
private final ArrayList<CardView> cards = new ArrayList<CardView>();
private Context context;
public CardAdapter( Context context ) {
this.context = context;
}
public void add( CardView view ) {
cards.add( view );
}
#Override
public int getCount() {
return cards.size();
}
#Override
public int getViewTypeCount() {
return getCount();
}
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public Object getItem(int i) {
if( i >= cards.size() || i < 0 )
return null;
return cards.get( i );
}
#Override
public long getItemId(int i) {
return cards.get(i).getId();
}
#Override
public View getView(int position, View view, ViewGroup viewGroup) {
if( view == null ) {
view = (CardView) getItem( position );
}
return view;
}
}
The MainActivity is something like:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView list = findViewById(R.id.notesList);
CardAdapter adapter = new CardAdapter(getBaseContext());
adapter.add( makeCard() );
// repeat making cards however many times...
list.setAdapter(adapter);
}
private CardView makeCard() {
CardView card = new CardView(this);
// Do some things to customize the CardView
return card;
}
}
Now it seems like Android is trying to recycle the views, but is doing a lousy job of it. Initially the page looks fine, but as soon as I begin scrolling, the cards start to move all over the screen, the spacing changes, the cards begin to overlap, etc. .
Note that I only scrolled for a couple seconds, this was not a gradual change, the cards were kind of 'jumping' around the screen. The only solution I have come up with so far is changing my getView to be:
#Override
public View getView(int position, View view, ViewGroup viewGroup) {
return (CardView) getItem(position);
}
And this works, but even loading ~10 items is very slow, and the scrolling lags.
Kindly use RecyclerView it uses view holder pattern that resolve all these lagging issues
Change your implementation from ListView to RecyclerView.
RecyclerView is a ViewGroup added to the android studio as a successor
of the GridView and ListView. It is an improvement on both of them and
can be found in the latest v-7 support packages. It has been created
to make possible construction of any lists with XML layouts as an item
which can be customized vastly while improving on the efficiency of
ListViews and GridViews. This improvement is achieved by recycling the
views which are out of the visibility of the user. For example, if a
user scrolled down to a position where the items 4 and 5 are visible;
items 1, 2 and 3 would be cleared from the memory to reduce memory
consumption.
From GeeksforGeeks.
Ps: the link has a lot of explanation, tutorial and codes...
The RecyclerView was created specifically to solve the kind of problem you're having. It has a lot of improvements over ListView, like less memory consumption, less lagging, better scrooling...
ok,its too simple
first remember u must pass what u want use in adapter,but now u just pass a (this) to this adapter.
use code below for your Constructor .
have good codding.
private final ArrayList<CardView> cards = new ArrayList<CardView>();
private Context context;
public CardAdapter( Context context , ArrayList<CardView> cards) {
this.context = context;
this.cards =cards
}
as in topic i have huge problem with optymalization of my activity.
wireframe
At root level we have three controls:
- Tab Layout, related with ViewPager
- SurfaceView displaying plot for every fragment
- and ViewPager itself
ViewPager displays fragments marked on the picture with red color. Every Fragment contains child fragment(green border) displaying List of elements with some controls responsible for edition. Elements are shown in ListView with custom adapter. I have two problems with this view.
First problem:
I am using FragmenStatePagerAdapter for displaying views. I am storing instances of already created fragments inside adapter Map, to reuse them.
protected Map<Integer, Fragment> fragmentReferenceMap = new HashMap<Integer, Fragment>();
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
if(!fragmentReferenceMap.containsKey(position)) {
econetFragmentReferenceMap.put(position, MyFragment.newInstance(position));
}
return fragmentReferenceMap.get(position);
}
When I swipe back to once created Fragment adapter returns created instance from map but even with that, onCreate() on my Fragment is called and whole fragment is created once again. I know that ViewPager has option for setting offscreen limit but isn't there any way to reuse this fragment view?
The second problem is that the whole proces of inflating ListView take to long. I tried changing offScreenLimit but when it is set to 7(max number of view pages) app freeze for a few seconds before showing activity, and i see lags in swipe animations. When I set it to other number lik 1 or 0, activity creating is faster but I loss a lot of frames when swiping. I am using all fragments data are stored in single data table. This approach is ease what to store all changes even if one of fragment is destroyed by ViewPager, so I would like to stay with that. I tried mocking data for adapter and I found that it is not making big affect on its creation time. I have also tires using async task to getView method for gathering whole data for single row and in onPostExecute method add it to view but it not giving satisfing result
This is my getView method:
#NonNull
#Override
public View getView(final int position, #Nullable View convertView, #NonNull ViewGroup parent) {
convertView = super.getView(position,convertView,parent);
Spinner spinner = (Spinner) convertView.findViewById(R.id.UserMode);
final TextView startTime = (TextView) convertView.findViewById(R.id.rangeStart);
final TextView stopTime = (TextView) convertView.findViewById(R.id.rangeStop);
if(getConverter() != null){
List<String> values = getConverter().getValuesLabels();
ArrayAdapter spinerAdapter = new ArrayAdapter(context, R.layout.custom_sec_spinner, R.id.tvSpinnerText, values);
spinerAdapter.setDropDownViewResource(R.layout.spinner_dodatkowe_wiersz);
spinner.setAdapter(spinerAdapter);
spinner.setSelection(getConverter().getUserMode(modeIndex, position));
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
getConverter().setUserMode(modeIndex, position, position) ;
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
int[] start = getConverter().getRangeStart(modeIndex, position);
int[] end = getConverter().getRangeEnd(modeIndex, position);
startTime.setText(start[0] + ":" + start[1]);
stopTime.setText(end[0] + ":" + end[1]);
startTime.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
TimePickerDialog mTimePicker;
int[] selectedTime = getConverter().getRangeStart(modeIndex, position);
mTimePicker = new TimePickerDialog(getContext(), new TimePickerDialog.OnTimeSetListener() {
#Override
public void onTimeSet(TimePicker timePicker, int selectedHour, int selectedMinute) {
getConverter().setRangeStart(modeIndex, position, new int[]{selectedHour, selectedMinute});
startTime.setText(selectedHour + ":" + selectedMinute);
}
}, selectedTime[0], selectedTime[1], true);
mTimePicker.show();
}
});
stopTime.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
TimePickerDialog mTimePicker;
int[] selectedTime = getConverter().getRangeEnd(modeIndex, position);
mTimePicker = new TimePickerDialog(getContext(), new TimePickerDialog.OnTimeSetListener() {
#Override
public void onTimeSet(TimePicker timePicker, int selectedHour, int selectedMinute) {
getConverter().setRangeEnd(modeIndex, position, new int[]{selectedHour, selectedMinute});
stopTime.setText(selectedHour + ":" + selectedMinute);
}
}, selectedTime[0], selectedTime[1], true);
mTimePicker.show();
}
});
}
else{
spinner.setEnabled(false);
}
return convertView;
}
So, could you please tell me how can I optimaze my adapter and ListView to make it work faster, or give user the experience that nothing is lagging? I would be gratefull for any advice or even links to similar articles because I've been searching for whole day and tried almost everything. Thank you :)
Create list view on a background thread.
Using a background thread ("worker thread") removes strain from the main thread so it can focus on drawing the UI. In many cases, using AsyncTask provides a simple way to perform your work outside the main thread. AsyncTask automatically queues up all the execute() requests and performs them serially. This behavior is global to a particular process and means you don’t need to worry about creating your own thread pool. reference link
Try to Use Holder Class to increase performance
A ViewHolder object stores each of the component views inside the tag field of the Layout, so you can immediately access them without the need to look them up repeatedly. First, you need to create a class to hold your exact set of views.
View row=view;
Holder holder=null;
if(row==null){
LayoutInflater inflater= (LayoutInflater)mContext.getSystemService(mContext.LAYOUT_INFLATER_SERVICE);
row= inflater.inflate(R.layout.half_movie,viewGroup,false);
holder=new Holder(row);
row.setTag(holder);
}else{
holder= (Holder) row.getTag();
}
I'm trying to implement a similar feature that Google Calendar has.
Basically, I have two views:
View A)
ViewPager with CustomViews of calendars. Let's call this MonthViewPager which extends ViewPager
View B)
-- On the top of the ViewPager -- a Year View which says what month the View A) has to go. Let's call this YearView.
Scope: I want the user to be able to "infinitely" scroll along the months desired, and being able to switch month from the YearView.
The way I initialize my ViewPager is this:
public MonthViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
this.attrs = attrs;
setAdapter(new EndLessAdapter(Calendar.getInstance()));
setCurrentItem(Integer.MAX_VALUE / 2);
}
which takes this performance:
I've been alternating the number of the pages kept offscreen in an idle state with setOffscreenPageLimit(int limit). I've tried with 1, 3, 12...
My PagerAdapter is this one:
public class EndLessAdapter extends PagerAdapter {
Calendar calendar;
public EndLessAdapter(Calendar calendar) {
this.calendar = calendar;
}
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public CalendarView instantiateItem(ViewGroup collection, int position) {
calendar.set(Calendar.MONTH, calendar.get(Calendar.MONTH) + position - Integer.MAX_VALUE/2);
final CalendarView calendarView = new CalendarView(context, attrs);
((ViewPager) collection).addView(calendarView, 0, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
return calendarView;
}
#Override
public void destroyItem(ViewGroup 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;
}
}
but then when I want to set a month from YearView (for example December), if you see my adapter, I set the position 8 + Integer.MAX_VALUE / 2 which is 8 + April
I do it like:
int differenceMonths = Utils.differenceMonthsToNow(date);
monthViewPager.setCurrentItem(differenceMonths + Integer.MAX_VALUE / 2, false);
and this is the performance:
the UI gets blocked for all that time.
I debugged everything with the Android Monitor, and I saw that 99.0% of the time was taking time on:
ViewPager.setCurrentItem(int item, boolean smoothScroll):
and that the method instantiateItem is calling every single view. (I think it shouldn't be like this and maybe it's a bug).
Alternatively I tried to embed everything on a Fragment and change PageAdapter by FragmentStatePagerAdapter but the result was really similar.
What's going on with my code? Isn't it possible to skip, for example, 1000 pages without avoiding to draw each one?
Thanks to have taken the whole reading ;).
setCurrentItem(int) uses a smooth scroll, so that means that it will scroll through ALL the views until it arrives on the one that you asked for, so that means that all the views have to be drawn.
Isn't it possible to skip, for example, 1000 pages without avoiding to draw each one?
well, the solution here would be to make it without the scroll, setCurrentItem(int, false); that way the ViewPager will only draw that item and skip all the items in-between.
Hope it helps.
I have a very simple PagerAdapter that for some reason, removes the views at position 0 and
1 when it is scrolled to position 2.
Specifically, when I first run the app, there are 3 views. I scroll to position 2 and position 1's view will disappear. View 0 is still there. If I scroll to view 0 and back to view 2 and again back to view 0, View 0 suddenly disappears. I do the same again, and I can actually see view 0 being instantiated and immediately destroyed.
what is going on here?
Main Activity
public class MainActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final MyPagerAdapter adapter = new MyPagerAdapter(this);
final ViewPager myPager = (ViewPager) findViewById(R.id.mypanelpager);
myPager.setAdapter(adapter);
myPager.setCurrentItem(1);
}
public class MyPagerAdapter extends PagerAdapter {
private Context ctx;
Calendar c = Calendar.getInstance();
int month = c.get(Calendar.MONTH);
int year = c.get(Calendar.YEAR);
ViewGroup collection;
public MyPagerAdapter (Context ctx){
this.ctx = ctx ;
}
#Override
public int getCount() {
return 3;
}
public Object instantiateItem(ViewGroup container, int position ){
this.collection = (ViewPager)container;
NewMonth monthObject = new NewMonth(ctx, month+position-1,2012);
View monthLayout = monthObject.newParentLayout;
collection.addView(monthLayout);
return monthLayout;
return addViewAt(position);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
collection.removeViewAt(position);
}
#Override
public Parcelable saveState() {
return null;
}
#Override
public boolean isViewFromObject(View view, Object arg1) {
return view==arg1;
}
}
You have to maintain all tabs in memory specifying the OffscreenPageLimit to N-1, in your case put this inside the onCreate method:
myPager.setOffscreenPageLimit(2);
Checkout populate() function from ViewPager source - it has clear checks then to remove currentIndex+1 and currentIndex-1 items. Removing is done based on view sizes and it seems to be entirely internal ViewPager logic. ViewPager source is located
<android sdk folder>\extras\android\support\v4\src\java\android\support\v4\view\ViewPager.java
.
You might debug ViewPager to know that is happening exactly, but I wouldn't suggest so until You faced some serious issue with ViewPager. What's why: if you dig into ViewPager code it might lead You to write some hackish code (even not intentionally) on it based on its internal structure and not on its public interface and documentation. So, the main idea of data encapsulation would be ruined which is definitely not good (sadly, sometimes we need to do so in Android due to lack of documentation / unclear naming / android internal issues etc., check Code Complete by Steve McConnell for more details on good encapsulation etc.).
Here the key is that position is different then index. If they give you a position there is no guarantee that your collections haven't removed other positions.
Example:
Let's say your at position 2. destroyItem might have been called for position 0 which means in your collections now position 2 is actually at index 1. So your indexes will quickly become out of sync from your positions. The same thing happens with listview children. I would recommend using a sparseArray instead.
private final SparseArray<View> views = new SparseArray<>();
public View instantiateItem(ViewGroup container, final int position) {
...
views.put(position, view);
...
}
public void destroyItem(ViewGroup container, int position, Object object) {
View view = views.get(position);
if (view != null) {
container.removeView(view);
views.remove(position);
}
}
I have infinite gallery based on this example :
http://blog.blundell-apps.com/infinite-scrolling-gallery/ ,
It runs nicely, now I want to have the Gallery still scroll through the images and under each image there should be a text caption.
I searched net with no result, would you please help me in coding that, just beginner in development.
==================================================================================
NEW Update :
upload photo explane what i need exactly which i get it with normal gallery (applied text to each image and able to customize text too as shown down image ,and each image has diffrenet text than others , but still not succeed to do it with infinite gallery :
PLEASE ANY HELP AND ADVICE .
THANKS ALOT.
I went through Blundell's tutorial and thanks to him I know how to make an Infinitelyscrolling gallery :)
To answer the question, about how to add a text caption below each of the images , I made same small changes to Blundell's nice tut and used some of his suggestions in the above answer and I think I got a nice way of doing the task.
The code below doesnt inflate or use gallery_item.xml at all, so it will increase the performance significantly compared to the way when you are inflating it every time.
Trimmed down code of classes from Blundell's tutorial ( because in the question, you are using only resources and not sdcard).
public class InfiniteScrollingGalleryActivity extends Activity {
public class GalleryItem{
int imageId;
String caption;
public int getImageId() {
return imageId; }
public String getCaption() {
return caption;
}
public GalleryItem(int i,String s) {
imageId=i;
caption=s; }
}
int[] resourceImages = {R.drawable.ic_launcher,R.drawable.ic_launcher,R.drawable.ic_launcher,
R.drawable.ic_launcher,R.drawable.ic_launcher,R.drawable.ic_launcher};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GalleryItem[] item = new GalleryItem[6];
//initialising all items, change member variables according to needs
for(int i=0;i<6;i++){
item[i] = new GalleryItem(resourceImages[i], "pic no" +(i+1)); }
setContentView(R.layout.activity_main);
InfiniteGallery galleryOne = (InfiniteGallery) findViewById(R.id.galleryOne);
galleryOne.setResourceGallery(item);
} }
Here I have added the GalleryItem class array and passed it.
Also added the below code in InfiniteGalley class.
public void setResourceGallery(GalleryItem[] item) {
setAdapter(new InfiniteGalleryResourceAdapter(getContext(), item));
setSelection((getCount() / 2));
}
below code's getView() is where the good things happen :
public class InfiniteGalleryResourceAdapter extends BaseAdapter {
/** The context your gallery is running in (usually the activity) */
private Context mContext;
GalleryItem[] myItems;
public InfiniteGalleryResourceAdapter(Context context, GalleryItem[] item) {
this.mContext = context;
myItems=item;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// convertView is always null in android.widget.Gallery
TextView t = new TextView(mContext);
try {
int itemPos = (position % myItems.length);
t.setText(myItems[itemPos].getCaption());
Drawable d = mContext.getResources().getDrawable(myItems[itemPos].getImageId());
((BitmapDrawable) d).setAntiAlias(true); // Make sure we set anti-aliasing otherwise we get jaggies (non-smooth lines)
//d.setBounds(0,0,60,60); //use this to change dimens of drawable,if needed
t.setCompoundDrawablesWithIntrinsicBounds(null, d, null, null);
} catch (OutOfMemoryError e) {
// a 'just in case' scenario
Log.e("InfiniteGalleryResourceAdapter", "Out of memory creating imageview. Using empty view.", e);
}
return t;
}
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public long getItemId(int position) {
return position;
}
/** The width of each child image */
private static final int G_ITEM_WIDTH = 120;
/** The height of each child image */
private static final int G_ITEM_HEIGHT = 80;
private int imageWidth;
private int imageHeight;
}
In getView(), I am just creating a textView and assigning the drawable to it using the handy t.setCompoundDrawablesWithIntrinsicBounds(null, d, null, null); . So it excludes the need of inflating layouts which is a heavy operation.
Below is the output image:
In the adapter you can see the method: getView, you can see this method returns an ImageView, so now you want the getView method to return an imageview and textview...
U can do this in a few ways, here how you can do it with a LayoutInflater
View v = getLayoutInflater().inflate(R.layout.gallery_item, null);
((ImageView) v.findViewById(R.id.img)).setImageResource(imageIds[position]);
((TextView) v.findViewById(R.id.caption)).setText(captions[position]);
So in your res/layout folder you should have an 'gallery_item' layout that contains an ImageView (img) and a TextView (caption)
i.e.
gallery_item.xml
<LinearLayout>
<ImageView ... />
<TextView ... />
</LinearLayout>
Hope this was helpfull!
EDIT
so as the above example shows you would need two arrays, one of imageIds and one of textCaptions. To keep your Adapter nice and clean it's screaming for you to make an object.
public class GalleryItem {
int imageId;
String caption
// Constructor
// getters and setters
}
You could then pass an Array or List of your GalleryItems to the Adapter (replacing the setAdapter method). i.e:
GalleryItem[] items = new GalleryItem[];
Then in your getView method as outlined above you would extract each object:
GalleryItem item = items[position];
View v = getLayoutInflater().inflate(R.layout.gallery_item, null);
((ImageView) v.findViewById(R.id.img)).setImageResource(item.getImageId());
((TextView) v.findViewById(R.id.caption)).setText(item.getCaption());
Hope thats clear