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 withTextView
s of day numbers.
- A topbar which is a
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;
}
}
}
}