AdapterViewAnimator.java revision 8322834a2544a673a35c5d4ad0d5909b3ca37600
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import java.util.ArrayList;
20import java.util.HashMap;
21import java.util.LinkedList;
22
23import android.animation.PropertyAnimator;
24import android.content.Context;
25import android.content.Intent;
26import android.content.res.TypedArray;
27import android.graphics.Rect;
28import android.os.Handler;
29import android.os.Looper;
30import android.util.AttributeSet;
31import android.util.Log;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.ViewGroup.LayoutParams;
35import android.view.animation.Animation;
36import android.view.animation.AnimationUtils;
37
38/**
39 * Base class for a {@link AdapterView} that will perform animations
40 * when switching between its views.
41 *
42 * @attr ref android.R.styleable#AdapterViewAnimator_inAnimation
43 * @attr ref android.R.styleable#AdapterViewAnimator_outAnimation
44 * @attr ref android.R.styleable#AdapterViewAnimator_animateFirstView
45 */
46public abstract class AdapterViewAnimator extends AdapterView<Adapter>
47        implements RemoteViewsAdapter.RemoteAdapterConnectionCallback{
48    private static final String TAG = "RemoteViewAnimator";
49
50    /**
51     * The index of the current child, which appears anywhere from the beginning
52     * to the end of the current set of children, as specified by {@link #mActiveOffset}
53     */
54    int mWhichChild = 0;
55
56    /**
57     * Whether or not the first view(s) should be animated in
58     */
59    boolean mAnimateFirstTime = true;
60
61    /**
62     *  Represents where the in the current window of
63     *  views the current <code>mDisplayedChild</code> sits
64     */
65    int mActiveOffset = 0;
66
67    /**
68     * The number of views that the {@link AdapterViewAnimator} keeps as children at any
69     * given time (not counting views that are pending removal, see {@link #mPreviousViews}).
70     */
71    int mNumActiveViews = 1;
72
73    /**
74     * Array of the children of the {@link AdapterViewAnimator}. This array
75     * is accessed in a circular fashion
76     */
77    View[] mActiveViews;
78
79    /**
80     * List of views pending removal from the {@link AdapterViewAnimator}
81     */
82    ArrayList<View> mPreviousViews;
83
84    /**
85     * The index, relative to the adapter, of the beginning of the window of views
86     */
87    int mCurrentWindowStart = 0;
88
89    /**
90     * The index, relative to the adapter, of the end of the window of views
91     */
92    int mCurrentWindowEnd = -1;
93
94    /**
95     * The same as {@link #mCurrentWindowStart}, except when the we have bounded
96     * {@link #mCurrentWindowStart} to be non-negative
97     */
98    int mCurrentWindowStartUnbounded = 0;
99
100    /**
101     * Indicates whether to treat the adapter to be a circular structure, ie.
102     * the view before 0 is considered to be <code>mAdapter.getCount() - 1</code>
103     *
104     * TODO: this doesn't do anything yet
105     *
106     */
107    boolean mCycleViews = false;
108
109    /**
110     * Handler to post events to the main thread
111     */
112    Handler mMainQueue;
113
114    /**
115     * Listens for data changes from the adapter
116     */
117    AdapterDataSetObserver mDataSetObserver;
118
119    /**
120     * The {@link Adapter} for this {@link AdapterViewAnimator}
121     */
122    Adapter mAdapter;
123
124    /**
125     * The {@link RemoteViewsAdapter} for this {@link AdapterViewAnimator}
126     */
127    RemoteViewsAdapter mRemoteViewsAdapter;
128
129    /**
130     * Specifies whether this is the first time the animator is showing views
131     */
132    boolean mFirstTime = true;
133
134    /**
135     * TODO: Animation stuff is still in flux, waiting on the new framework to settle a bit.
136     */
137    Animation mInAnimation;
138    Animation mOutAnimation;
139    private  ArrayList<View> mViewsToBringToFront;
140
141    public AdapterViewAnimator(Context context) {
142        super(context);
143        initViewAnimator(context, null);
144    }
145
146    public AdapterViewAnimator(Context context, AttributeSet attrs) {
147        super(context, attrs);
148
149        TypedArray a = context.obtainStyledAttributes(attrs,
150                com.android.internal.R.styleable.ViewAnimator);
151        int resource = a.getResourceId(
152                com.android.internal.R.styleable.ViewAnimator_inAnimation, 0);
153        if (resource > 0) {
154            setInAnimation(context, resource);
155        }
156
157        resource = a.getResourceId(com.android.internal.R.styleable.ViewAnimator_outAnimation, 0);
158        if (resource > 0) {
159            setOutAnimation(context, resource);
160        }
161
162        boolean flag = a.getBoolean(
163                com.android.internal.R.styleable.ViewAnimator_animateFirstView, true);
164        setAnimateFirstView(flag);
165
166        a.recycle();
167
168        initViewAnimator(context, attrs);
169    }
170
171    /**
172     * Initialize this {@link AdapterViewAnimator}
173     */
174    private void initViewAnimator(Context context, AttributeSet attrs) {
175        mMainQueue = new Handler(Looper.myLooper());
176        mActiveViews = new View[mNumActiveViews];
177        mPreviousViews = new ArrayList<View>();
178        mViewsToBringToFront = new ArrayList<View>();
179    }
180
181    /**
182     * This method is used by subclasses to configure the animator to display the
183     * desired number of views, and specify the offset
184     *
185     * @param numVisibleViews The number of views the animator keeps in the {@link ViewGroup}
186     * @param activeOffset This parameter specifies where the current index ({@link mWhichChild})
187     *        sits within the window. For example if activeOffset is 1, and numVisibleViews is 3,
188     *        and {@link setDisplayedChild} is called with 10, then the effective window will be
189     *        the indexes 9, 10, and 11. In the same example, if activeOffset were 0, then the
190     *        window would instead contain indexes 10, 11 and 12.
191     */
192     void configureViewAnimator(int numVisibleViews, int activeOffset) {
193        if (activeOffset > numVisibleViews - 1) {
194            // Throw an exception here.
195        }
196        mNumActiveViews = numVisibleViews;
197        mActiveOffset = activeOffset;
198        mActiveViews = new View[mNumActiveViews];
199        mPreviousViews.clear();
200        removeAllViewsInLayout();
201        mCurrentWindowStart = 0;
202        mCurrentWindowEnd = -1;
203    }
204
205    /**
206     * This class should be overridden by subclasses to customize view transitions within
207     * the set of visible views
208     *
209     * @param fromIndex The relative index within the window that the view was in, -1 if it wasn't
210     *        in the window
211     * @param toIndex The relative index within the window that the view is going to, -1 if it is
212     *        being removed
213     * @param view The view that is being animated
214     */
215    void animateViewForTransition(int fromIndex, int toIndex, View view) {
216        PropertyAnimator pa;
217        if (fromIndex == -1) {
218            pa = new PropertyAnimator(400, view, "alpha", 0.0f, 1.0f);
219            pa.start();
220        } else if (toIndex == -1) {
221            pa = new PropertyAnimator(400, view, "alpha", 1.0f, 0.0f);
222            pa.start();
223        }
224    }
225
226    /**
227     * Sets which child view will be displayed.
228     *
229     * @param whichChild the index of the child view to display
230     */
231    public void setDisplayedChild(int whichChild) {
232        if (mAdapter != null) {
233            mWhichChild = whichChild;
234            if (whichChild >= mAdapter.getCount()) {
235                mWhichChild = 0;
236            } else if (whichChild < 0) {
237                mWhichChild = mAdapter.getCount() - 1;
238            }
239
240            boolean hasFocus = getFocusedChild() != null;
241            // This will clear old focus if we had it
242            showOnly(mWhichChild);
243            if (hasFocus) {
244                // Try to retake focus if we had it
245                requestFocus(FOCUS_FORWARD);
246            }
247        }
248    }
249
250    /**
251     * Return default inAnimation. To be overriden by subclasses.
252     */
253    Animation getDefaultInAnimation() {
254        return null;
255    }
256
257    /**
258     * Return default outAnimation. To be overridden by subclasses.
259     */
260    Animation getDefaultOutAnimation() {
261        return null;
262    }
263
264    /**
265     * To be overridden by subclasses. This method applies a view / index specific
266     * transform to the child view.
267     *
268     * @param child
269     * @param relativeIndex
270     */
271    void applyTransformForChildAtIndex(View child, int relativeIndex) {
272    }
273
274    /**
275     * Returns the index of the currently displayed child view.
276     */
277    public int getDisplayedChild() {
278        return mWhichChild;
279    }
280
281    /**
282     * Manually shows the next child.
283     */
284    public void showNext() {
285        setDisplayedChild(mWhichChild + 1);
286    }
287
288    /**
289     * Manually shows the previous child.
290     */
291    public void showPrevious() {
292        setDisplayedChild(mWhichChild - 1);
293    }
294
295    /**
296     * Shows only the specified child. The other displays Views exit the screen,
297     * optionally with the with the {@link #getOutAnimation() out animation} and
298     * the specified child enters the screen, optionally with the
299     * {@link #getInAnimation() in animation}.
300     *
301     * @param childIndex The index of the child to be shown.
302     * @param animate Whether or not to use the in and out animations, defaults
303     *            to true.
304     */
305    void showOnly(int childIndex, boolean animate) {
306        showOnly(childIndex, animate, false);
307    }
308
309    private int modulo(int pos, int size) {
310        return (size + (pos % size)) % size;
311    }
312
313    /**
314     * Get the view at this index relative to the current window's start
315     *
316     * @param relativeIndex Position relative to the current window's start
317     * @return View at this index, null if the index is outside the bounds
318     */
319    View getViewAtRelativeIndex(int relativeIndex) {
320        if (relativeIndex >= 0 && relativeIndex <= mNumActiveViews - 1) {
321            int index = mCurrentWindowStartUnbounded + relativeIndex;
322            return mActiveViews[modulo(index, mNumActiveViews)];
323        }
324        return null;
325    }
326
327    private LayoutParams createOrReuseLayoutParams(View v) {
328        final LayoutParams currentLp = (LayoutParams) v.getLayoutParams();
329        if (currentLp instanceof LayoutParams) {
330            return currentLp;
331        }
332        return new LayoutParams(v);
333    }
334
335    void showOnly(int childIndex, boolean animate, boolean onLayout) {
336        if (mAdapter == null) return;
337
338        for (int i = 0; i < mPreviousViews.size(); i++) {
339            View viewToRemove = mPreviousViews.get(i);
340            viewToRemove.clearAnimation();
341            // applyTransformForChildAtIndex here just allows for any cleanup
342            // associated with this view that may need to be done by a subclass
343            applyTransformForChildAtIndex(viewToRemove, -1);
344            removeViewInLayout(viewToRemove);
345        }
346        mPreviousViews.clear();
347        int newWindowStartUnbounded = childIndex - mActiveOffset;
348        int newWindowEndUnbounded = newWindowStartUnbounded + mNumActiveViews - 1;
349        int newWindowStart = Math.max(0, newWindowStartUnbounded);
350        int newWindowEnd = Math.min(mAdapter.getCount(), newWindowEndUnbounded);
351
352        // This section clears out any items that are in our mActiveViews list
353        // but are outside the effective bounds of our window (this is becomes an issue
354        // at the extremities of the list, eg. where newWindowStartUnbounded < 0 or
355        // newWindowEndUnbounded > mAdapter.getCount() - 1
356        for (int i = newWindowStartUnbounded; i < newWindowEndUnbounded; i++) {
357            if (i < newWindowStart || i > newWindowEnd) {
358                int index = modulo(i, mNumActiveViews);
359                if (mActiveViews[index] != null) {
360                    View previousView = mActiveViews[index];
361                    mPreviousViews.add(previousView);
362                    int previousViewRelativeIndex = modulo(index - mCurrentWindowStart,
363                            mNumActiveViews);
364                    animateViewForTransition(previousViewRelativeIndex, -1, previousView);
365                }
366            }
367        }
368
369        // If the window has changed
370        if (! (newWindowStart == mCurrentWindowStart && newWindowEnd == mCurrentWindowEnd)) {
371            // Run through the indices in the new range
372            for (int i = newWindowStart; i <= newWindowEnd; i++) {
373
374                int oldRelativeIndex = i - mCurrentWindowStartUnbounded;
375                int newRelativeIndex = i - newWindowStartUnbounded;
376                int index = modulo(i, mNumActiveViews);
377
378                // If this item is in the current window, great, we just need to apply
379                // the transform for it's new relative position in the window, and animate
380                // between it's current and new relative positions
381                if (i >= mCurrentWindowStart && i <= mCurrentWindowEnd) {
382                    View view = mActiveViews[index];
383                    applyTransformForChildAtIndex(view, newRelativeIndex);
384                    animateViewForTransition(oldRelativeIndex, newRelativeIndex, view);
385
386                // Otherwise this view is new, so first we have to displace the view that's
387                // taking the new view's place within our cache (a circular array)
388                } else {
389                    if (mActiveViews[index] != null) {
390                        View previousView = mActiveViews[index];
391                        mPreviousViews.add(previousView);
392                        int previousViewRelativeIndex = modulo(index - mCurrentWindowStart,
393                                mNumActiveViews);
394                        animateViewForTransition(previousViewRelativeIndex, -1, previousView);
395
396                        if (mCurrentWindowStart > newWindowStart) {
397                            mViewsToBringToFront.add(previousView);
398                        }
399                    }
400
401                    // We've cleared a spot for the new view. Get it from the adapter, add it
402                    // and apply any transform / animation
403                    View newView = mAdapter.getView(i, null, this);
404                    if (newView != null) {
405                        mActiveViews[index] = newView;
406                        addViewInLayout(newView, -1, createOrReuseLayoutParams(newView));
407                        applyTransformForChildAtIndex(newView, newRelativeIndex);
408                        animateViewForTransition(-1, newRelativeIndex, newView);
409                    }
410                }
411                mActiveViews[index].bringToFront();
412            }
413
414            for (int i = 0; i < mViewsToBringToFront.size(); i++) {
415                View v = mViewsToBringToFront.get(i);
416                v.bringToFront();
417            }
418            mViewsToBringToFront.clear();
419
420            mCurrentWindowStart = newWindowStart;
421            mCurrentWindowEnd = newWindowEnd;
422            mCurrentWindowStartUnbounded = newWindowStartUnbounded;
423        }
424
425        mFirstTime = false;
426        if (!onLayout) {
427            requestLayout();
428            invalidate();
429        } else {
430            // If the Adapter tries to layout the current view when we get it using getView
431            // above the layout will end up being ignored since we are currently laying out, so
432            // we post a delayed requestLayout and invalidate
433            mMainQueue.post(new Runnable() {
434                @Override
435                public void run() {
436                    requestLayout();
437                    invalidate();
438                }
439            });
440        }
441    }
442
443    @Override
444    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
445        boolean dataChanged = mDataChanged;
446        if (dataChanged) {
447            handleDataChanged();
448
449            // if the data changes, mWhichChild might be out of the bounds of the adapter
450            // in this case, we reset mWhichChild to the beginning
451            if (mWhichChild >= mAdapter.getCount())
452                mWhichChild = 0;
453
454            showOnly(mWhichChild, true, true);
455        }
456
457        final int childCount = getChildCount();
458        for (int i = 0; i < childCount; i++) {
459            final View child = getChildAt(i);
460
461            int childRight = mPaddingLeft + child.getMeasuredWidth();
462            int childBottom = mPaddingTop + child.getMeasuredHeight();
463            LayoutParams lp = (LayoutParams) child.getLayoutParams();
464
465            child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
466                    childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
467        }
468        mDataChanged = false;
469    }
470
471    @Override
472    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
473        final int count = getChildCount();
474
475        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
476        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
477
478        for (int i = 0; i < count; i++) {
479            final View child = getChildAt(i);
480
481            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
482
483            lp.width = widthSpecSize - mPaddingLeft - mPaddingRight;
484            lp.height = heightSpecSize - mPaddingTop - mPaddingBottom;
485
486            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width,
487                    MeasureSpec.EXACTLY);
488            int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height,
489                    MeasureSpec.EXACTLY);
490
491            child.measure(childWidthMeasureSpec, childheightMeasureSpec);
492        }
493        setMeasuredDimension(widthSpecSize, heightSpecSize);
494    }
495
496    /**
497     * Shows only the specified child. The other displays Views exit the screen
498     * with the {@link #getOutAnimation() out animation} and the specified child
499     * enters the screen with the {@link #getInAnimation() in animation}.
500     *
501     * @param childIndex The index of the child to be shown.
502     */
503    void showOnly(int childIndex) {
504        final boolean animate = (!mFirstTime || mAnimateFirstTime);
505        showOnly(childIndex, animate);
506    }
507
508    /**
509     * Returns the View corresponding to the currently displayed child.
510     *
511     * @return The View currently displayed.
512     *
513     * @see #getDisplayedChild()
514     */
515    public View getCurrentView() {
516        return getViewAtRelativeIndex(mActiveOffset);
517    }
518
519    /**
520     * Returns the current animation used to animate a View that enters the screen.
521     *
522     * @return An Animation or null if none is set.
523     *
524     * @see #setInAnimation(android.view.animation.Animation)
525     * @see #setInAnimation(android.content.Context, int)
526     */
527    public Animation getInAnimation() {
528        return mInAnimation;
529    }
530
531    /**
532     * Specifies the animation used to animate a View that enters the screen.
533     *
534     * @param inAnimation The animation started when a View enters the screen.
535     *
536     * @see #getInAnimation()
537     * @see #setInAnimation(android.content.Context, int)
538     */
539    public void setInAnimation(Animation inAnimation) {
540        mInAnimation = inAnimation;
541    }
542
543    /**
544     * Returns the current animation used to animate a View that exits the screen.
545     *
546     * @return An Animation or null if none is set.
547     *
548     * @see #setOutAnimation(android.view.animation.Animation)
549     * @see #setOutAnimation(android.content.Context, int)
550     */
551    public Animation getOutAnimation() {
552        return mOutAnimation;
553    }
554
555    /**
556     * Specifies the animation used to animate a View that exit the screen.
557     *
558     * @param outAnimation The animation started when a View exit the screen.
559     *
560     * @see #getOutAnimation()
561     * @see #setOutAnimation(android.content.Context, int)
562     */
563    public void setOutAnimation(Animation outAnimation) {
564        mOutAnimation = outAnimation;
565    }
566
567    /**
568     * Specifies the animation used to animate a View that enters the screen.
569     *
570     * @param context The application's environment.
571     * @param resourceID The resource id of the animation.
572     *
573     * @see #getInAnimation()
574     * @see #setInAnimation(android.view.animation.Animation)
575     */
576    public void setInAnimation(Context context, int resourceID) {
577        setInAnimation(AnimationUtils.loadAnimation(context, resourceID));
578    }
579
580    /**
581     * Specifies the animation used to animate a View that exit the screen.
582     *
583     * @param context The application's environment.
584     * @param resourceID The resource id of the animation.
585     *
586     * @see #getOutAnimation()
587     * @see #setOutAnimation(android.view.animation.Animation)
588     */
589    public void setOutAnimation(Context context, int resourceID) {
590        setOutAnimation(AnimationUtils.loadAnimation(context, resourceID));
591    }
592
593    /**
594     * Indicates whether the current View should be animated the first time
595     * the ViewAnimation is displayed.
596     *
597     * @param animate True to animate the current View the first time it is displayed,
598     *                false otherwise.
599     */
600    public void setAnimateFirstView(boolean animate) {
601        mAnimateFirstTime = animate;
602    }
603
604    @Override
605    public int getBaseline() {
606        return (getCurrentView() != null) ? getCurrentView().getBaseline() : super.getBaseline();
607    }
608
609    @Override
610    public Adapter getAdapter() {
611        return mAdapter;
612    }
613
614    @Override
615    public void setAdapter(Adapter adapter) {
616        if (mAdapter != null && mDataSetObserver != null) {
617            mAdapter.unregisterDataSetObserver(mDataSetObserver);
618        }
619
620        mAdapter = adapter;
621
622        if (mAdapter != null) {
623            mDataSetObserver = new AdapterDataSetObserver();
624            mAdapter.registerDataSetObserver(mDataSetObserver);
625        }
626        setFocusable(true);
627    }
628
629    /**
630     * Sets up this AdapterViewAnimator to use a remote views adapter which connects to a
631     * RemoteViewsService through the specified intent.
632     *
633     * @param intent the intent used to identify the RemoteViewsService for the adapter to
634     *        connect to.
635     */
636    @android.view.RemotableViewMethod
637    public void setRemoteViewsAdapter(Intent intent) {
638        mRemoteViewsAdapter = new RemoteViewsAdapter(getContext(), intent, this);
639    }
640
641    @Override
642    public void setSelection(int position) {
643        setDisplayedChild(position);
644    }
645
646    @Override
647    public View getSelectedView() {
648        return getViewAtRelativeIndex(mActiveOffset);
649    }
650
651    /**
652     * Called back when the adapter connects to the RemoteViewsService.
653     */
654    public void onRemoteAdapterConnected() {
655        if (mRemoteViewsAdapter != mAdapter) {
656            setAdapter(mRemoteViewsAdapter);
657        }
658    }
659
660    /**
661     * Called back when the adapter disconnects from the RemoteViewsService.
662     */
663    public void onRemoteAdapterDisconnected() {
664        if (mRemoteViewsAdapter != mAdapter) {
665            mRemoteViewsAdapter = null;
666            setAdapter(mRemoteViewsAdapter);
667        }
668    }
669
670    static class LayoutParams extends ViewGroup.LayoutParams {
671        int horizontalOffset;
672        int verticalOffset;
673        View mView;
674
675        LayoutParams(View view) {
676            super(0, 0);
677            horizontalOffset = 0;
678            verticalOffset = 0;
679            mView = view;
680        }
681
682        LayoutParams(Context c, AttributeSet attrs) {
683            super(c, attrs);
684            horizontalOffset = 0;
685            verticalOffset = 0;
686        }
687
688        void setHorizontalOffset(int newHorizontalOffset) {
689            horizontalOffset = newHorizontalOffset;
690            if (mView != null) {
691                mView.requestLayout();
692                mView.invalidate();
693            }
694        }
695
696        private Rect parentRect = new Rect();
697        void invalidateGlobalRegion(View v, Rect r) {
698            View p = v;
699            boolean firstPass = true;
700            parentRect.set(0, 0, 0, 0);
701            while (p.getParent() != null && p.getParent() instanceof View
702                    && !parentRect.contains(r)) {
703                if (!firstPass) r.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY());
704                firstPass = false;
705                p = (View) p.getParent();
706                parentRect.set(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY(),
707                        p.getRight() - p.getScrollX(), p.getBottom() - p.getScrollY());
708            }
709            p.invalidate(r.left, r.top, r.right, r.bottom);
710        }
711
712        private Rect invalidateRect = new Rect();
713        // This is public so that PropertyAnimator can access it
714        public void setVerticalOffset(int newVerticalOffset) {
715            int offsetDelta = newVerticalOffset - verticalOffset;
716            verticalOffset = newVerticalOffset;
717            if (mView != null) {
718                mView.requestLayout();
719                int top = Math.min(mView.getTop() + offsetDelta, mView.getTop());
720                int bottom = Math.max(mView.getBottom() + offsetDelta, mView.getBottom());
721                invalidateRect.set(mView.getLeft(), top, mView.getRight(), bottom);
722                invalidateGlobalRegion(mView, invalidateRect);
723            }
724        }
725    }
726}
727