【问题标题】:Custom calendar view causes an animation to stutter on creation自定义日历视图导致动画在创建时结结巴巴
【发布时间】:2014-08-09 03:39:48
【问题描述】:

在我的应用程序中,目前有一个部分包含一个片段,该片段可以查询数据库以获取对象列表,然后显示它们(使用 ListView)。
当用户单击列表中的一个对象时,该部分的高度会变大以合并.replace()s ListView 片段并向上移动的不同片段。

这个新片段显示与用户单击的对象相关的信息。
这个片段中的一个视图是一个ViewPager,它只包含2个片段(最多):

  1. 一个计时器片段。
  2. 日历片段。

计时器片段是一个片段,它仅使用CountDownTimer 并更改TextView 的文本onTick()。此片段可能会显示也可能不会显示,具体取决于对象。

但是,日历片段包含一个更重的视图 - 自定义日历视图。
日历应该显示当前日期(现在)之前和之后一年中的日子。

日历片段(实际上是视图,因为片段中没有根本逻辑)导致当用户单击对象时触发的动画卡顿并且看起来很糟糕。这仅在第一次单击对象时发生。第二次更流畅了(我想这要归功于ViewPager保存了片段的状态,但我不确定......)。

我想知道代码中是否存在任何特定的瓶颈/问题会导致它的行为如此。我最好的猜测是一起加载的视图太多了。

我想到的第一个解决方案,基本上是在片段内移动动画,并在片段完全加载并准备好显示后触发它。然而,这意味着我将从片段本身内部控制父级(片段的容器)......不确定这是否是一个好的设计。不过,我可以为此创建一个侦听器,并将其调用放在onCreateView() 的末尾。

另一种可能的解决方案,这只是我的一个理论,但是在单独的线程上创建所有视图,然后将它们添加到主线程中的 UI 可能会稍微加快进程。虽然,我不确定这会多好(在性能方面),如果有的话,以及这是一种好的做法。

如何优化我的CalendarView(或者我的整个“对象视图片段”)以使动画正常工作?

CalendarView 包括:

  • 一个垂直的LinearLayout,包含2个子视图:
    1. 顶栏是TableLayout,其中包含7 天的名称。
    2. 一个GridView,其中填充了TextViews 天数。

一些代码:

MainActivity - 替换当前片段

@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();
}

习惯目标视图

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(这是View本身,而不是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;
        }

    }
}
}

【问题讨论】:

  • 问题是,当您只需要几个视图时,您创建的视图谁知道有多少。实现一个视图模式以提高效率。
  • @bbaker,不过,我正在回收视图。此外,日历中的每个视图都是TextView,因此不需要视图持有者,因为我猜没有任何子视图。感谢您的评论!
  • @xTCx 你能试试吗? => 以短暂的延迟触发ObjectAnimator - 比如500ms。将ObjectAnimator 声明为final。创建一个Handler 并将调用objAnim.start() 放在Runnable 中。将Runnable 发布到mHandler.postDelayed(mRunnable, 500L);。这样更好吗?在调用动画之前,片段(或视图)可能没有足够的时间进行初始化。如果延迟发布确实起作用,您可以在第一个事务中使用它,然后让 ViewPager 处理后续检索。

标签: android performance listview android-fragments calendar


【解决方案1】:

所以我终于找到了解决办法!最后,我的问题的根源在其他地方,但以防万一有人遇到同样类型的问题/口吃,我将根据我的知识分享解决问题的最佳方法 - 使用听众。在 Android 开发教程中也有推荐。

使用自定义侦听器接口,您可以让包含活动知道片段已完成所有加载。这样,您可以让动画仅在片段加载后运行,从而获得流畅的动画效果。

示例界面:

public interface OnFragmentLoadedListener {
    public abstract void OnFragmentLoaded(Object A);
}

然后您在包含该片段的活动中实现接口:

public class MainActivity extends Activity implements MyFragment.OnFragmentLoadedListener {
    ................

    public void OnFragmentLoaded(Object A) {
        //Do something...
    }

然后你需要在片段内部设置监听器。您可以创建一个类似setOnFragmentListener(OnFragmentListener mListener) 的方法,或者按照我的喜好,在Fragment 类的onAttach() 方法中获取对侦听器的引用:

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");
    }
}

最后,您需要让听众知道片段何时可​​以显示。
在我的情况下,那将是在加载另一个片段之后,因此我将侦听器调用放在包含(和包含)片段的接口实现中。

if(mListener != null)
    mListener.OnFragmentLoaded(A);

我的问题的根源真的是……JODA TIME

我的问题的根源实际上是我对 joda-time 库的使用。
我使用原始的 joda-time for java,这是一个错误。
一切正常,但在努力解决我的问题后,我想知道 joda-time 是否真的是问题所在,因为我的日历代码的其余部分非常类似于 android 提供的日历小部件。

令人惊讶的是,我遇到了这个 question/answer,这证明了我的理论是正确的。
起初,我其实很失望,因为我认为我必须回到使用 androids 的默认 Calendar 类,然后我记得有一个 joda for android 版本,并认为没有什么可失去的试试看。

无论如何,事实证明这个 android 版本确实创造了奇迹。它像魔术一样工作并加速了我的整个应用程序!你可以在这里找到这个很棒的图书馆:joda-time-android

我真的不知道为什么,这就是为什么我邀请任何知道的人在新答案中解释它,我很乐意将其标记为已接受。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-09-11
    • 2019-06-29
    • 1970-01-01
    • 1970-01-01
    • 2018-01-22
    • 1970-01-01
    • 2019-08-28
    • 1970-01-01
    相关资源
    最近更新 更多