自定义日历视图会导致动画在创建时断断续续

时间:2014-07-29 16:06:38

标签: android performance listview android-fragments calendar

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

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

  1. 计时器片段。
  2. 日历片段。
  3. 计时器片段是一个只使用CountDownTimer并更改TextView文本onTick()的片段。根据对象,可能会显示或不显示此片段。

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

    日历片段(或视图,实际上,因为片段中没有逻辑 )会导致当用户点击某个对象时出现的动画断断续续并且看起来很糟糕。这仅在第一次单击对象时发生。在第二次它更顺畅(我想它是由于ViewPager保存片段的状态,但我不确定......)。

    我想知道代码中是否存在任何特定的瓶颈/问题会导致它表现得那样。我最好的猜测是,有太多的视图被加载在一起。

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

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

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

    CalendarView包含:

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

    一些代码:

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

    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(这是查看本身,而不是片段!)

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

1 个答案:

答案 0 :(得分:2)

所以我终于找到了解决方案!最后,我的问题的根源在其他地方,但万一有人会遇到同样的问题/口吃,我会根据我的知识分享最好的解决方法 - 使用听众。它也被推荐在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)的方法,或者根据我的喜好,在onAttach()类的Fragment方法中获取对侦听器的引用:

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

最后,您需要让听众知道片段何时准备好显示 在我的情况下,那将是在加载ANOTHER片段之后,所以我将我的监听器调用放在包含(和包含)片段的接口实现中。

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

我的问题来源是...... JODA TIME

我的问题的根源实际上是我对joda-time库的使用 我用java的原始joda-time,这是一个错误 一切都运行良好,除了努力解决我的问题后,我想知道是否有可能joda-time实际上是问题,因为我的日历代码的其余部分非常像android提供的日历小部件。

令人惊讶的是,我遇到了这个 question/answer ,证明了我的理论正确。
起初,我实际上非常失望,因为我认为我必须回到使用机器人&#39;默认Calendar类,然后我记得有一个版本的joda for android,并且认为没有什么可以通过尝试来输掉。

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

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