AbstractMediaItemPresenter.java revision 78be4412362eafffe14b60a20b7ddd4bf86a515b
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.widget;
15
16
17import android.animation.ValueAnimator;
18import android.content.Context;
19import android.graphics.Color;
20import android.graphics.Rect;
21import android.support.v17.leanback.R;
22import android.support.v4.view.ViewCompat;
23import android.util.TypedValue;
24import android.view.ContextThemeWrapper;
25import android.view.LayoutInflater;
26import android.view.View;
27import android.view.ViewGroup;
28import android.view.animation.DecelerateInterpolator;
29import android.widget.TextView;
30import android.widget.ViewFlipper;
31
32import java.util.ArrayList;
33import java.util.List;
34
35/**
36 * Abstract {@link Presenter} class for rendering media items in a playlist format.
37 * Media item data provided for this presenter can implement the interface
38 * {@link MultiActionsProvider}, if the media rows wish to contain custom actions.
39 * Media items in the playlist are arranged as a vertical list with each row holding each media
40 * item's details provided by the user of this class and a set of optional custom actions.
41 * Each media item's details and actions are separately focusable.
42 * The appearance of each one of the media row components can be controlled through setting
43 * theme's attributes.
44 * Each media item row provides a view flipper for switching between different views depending on
45 * the playback state.
46 * A default layout is provided by this presenter for rendering different playback states, or a
47 * custom layout can be provided by the user by overriding the
48 * playbackMediaItemNumberViewFlipperLayout attribute in the currently specified theme.
49 * Subclasses should also override {@link #getMediaPlayState(Object)} to provide the current play
50 * state of their media item model in case they wish to use different views depending on the
51 * playback state.
52 * The presenter can optionally provide line separators between media rows by setting
53 * {@link #setHasMediaRowSeparator(boolean)} to true.
54 * <p>
55 *     Subclasses must override {@link #onBindMediaDetails} to implement their media item model
56 *     data binding to each row view.
57 * </p>
58 * <p>
59 *     The {@link OnItemViewClickedListener} and {@link OnItemViewSelectedListener}
60 *     can be used in the same fashion to handle selection or click events on either of
61 *     media details or each individual action views.
62 * </p>
63 * <p>
64 *     {@link AbstractMediaListHeaderPresenter} can be used in conjunction with this presenter in
65 *     order to display a playlist with a header view.
66 * </p>
67 */
68public abstract class AbstractMediaItemPresenter extends RowPresenter {
69
70    /**
71     * Different playback states of a media item
72     */
73
74    /**
75     * Indicating that the media item is currently neither playing nor paused.
76     */
77    public static final int PLAY_STATE_INITIAL = 0;
78    /**
79     * Indicating that the media item is currently paused.
80     */
81    public static final int PLAY_STATE_PAUSED = 1;
82    /**
83     * Indicating that the media item is currently playing
84     */
85    public static final int PLAY_STATE_PLAYING = 2;
86
87    final static Rect sTempRect = new Rect();
88    private int mBackgroundColor = Color.TRANSPARENT;
89    private boolean mBackgroundColorSet;
90    private boolean mMediaRowSeparator;
91    private int mThemeId;
92
93    private Presenter mMediaItemActionPresenter = new MediaItemActionPresenter();
94
95    /**
96     * Constructor used for creating an abstract media item presenter.
97     */
98    public AbstractMediaItemPresenter() {
99        this(0);
100    }
101
102    /**
103     * Constructor used for creating an abstract media item presenter.
104     * @param themeId The resource id of the theme that defines attributes controlling the
105     *                appearance of different widgets in a media item row.
106     */
107    public AbstractMediaItemPresenter(int themeId) {
108        mThemeId = themeId;
109        setHeaderPresenter(null);
110    }
111
112    /**
113     * Sets the theme used to style a media item row components.
114     * @param themeId The resource id of the theme that defines attributes controlling the
115     *                appearance of different widgets in a media item row.
116     */
117    public void setThemeId(int themeId) {
118        mThemeId = themeId;
119    }
120
121    /**
122     * Return The resource id of the theme that defines attributes controlling the appearance of
123     * different widgets in a media item row.
124     *
125     * @return The resource id of the theme that defines attributes controlling the appearance of
126     * different widgets in a media item row.
127     */
128    public int getThemeId() {
129        return mThemeId;
130    }
131
132    /**
133     * Sets the action presenter rendering each optional custom action within each media item row.
134     * @param actionPresenter the presenter to be used for rendering a media item row actions.
135     */
136    public void setActionPresenter(Presenter actionPresenter) {
137        mMediaItemActionPresenter = actionPresenter;
138    }
139
140    /**
141     * Return the presenter used to render a media item row actions.
142     *
143     * @return the presenter used to render a media item row actions.
144     */
145    public Presenter getActionPresenter() {
146        return mMediaItemActionPresenter;
147    }
148
149    /**
150     * The ViewHolder for the {@link AbstractMediaItemPresenter}. It references different views
151     * that place different meta-data corresponding to a media item details, actions, selector,
152     * listeners, and presenters,
153     */
154    public static class ViewHolder extends RowPresenter.ViewHolder {
155
156        private final View mMediaRowView;
157        private final View mSelectorView;
158        private final View mMediaItemDetailsView;
159        private final ViewFlipper mMediaItemNumberViewFlipper;
160        private final TextView mMediaItemNumberView;
161        private final View mMediaItemPausedView;
162
163        private final View mMediaItemPlayingView;
164        private final TextView mMediaItemNameView;
165        private final TextView mMediaItemDurationView;
166        private final View mMediaItemRowSeparator;
167        private final ViewGroup mMediaItemActionsContainer;
168        private final List<Presenter.ViewHolder> mActionViewHolders;
169        private MultiActionsProvider.MultiAction[] mMediaItemRowActions;
170        AbstractMediaItemPresenter mRowPresenter;
171        private ValueAnimator mFocusViewAnimator;
172
173        public ViewHolder(View view) {
174            super(view);
175            mSelectorView = view.findViewById(R.id.mediaRowSelector);
176            mMediaRowView  = view.findViewById(R.id.mediaItemRow);
177            mMediaItemDetailsView = view.findViewById(R.id.mediaItemDetails);
178            mMediaItemNameView = (TextView) view.findViewById(R.id.mediaItemName);
179            mMediaItemDurationView = (TextView) view.findViewById(R.id.mediaItemDuration);
180            mMediaItemRowSeparator = view.findViewById(R.id.mediaRowSeparator);
181            mMediaItemActionsContainer = (ViewGroup) view.findViewById(
182                    R.id.mediaItemActionsContainer);
183            mActionViewHolders = new ArrayList<Presenter.ViewHolder>();
184            getMediaItemDetailsView().setOnClickListener(new View.OnClickListener(){
185                @Override
186                public void onClick(View view) {
187                    if (getOnItemViewClickedListener() != null) {
188                        getOnItemViewClickedListener().onItemClicked(null, null,
189                                ViewHolder.this, getRowObject());
190                    }
191                }
192            });
193            getMediaItemDetailsView().setOnFocusChangeListener(new View.OnFocusChangeListener() {
194                @Override
195                public void onFocusChange(View view, boolean hasFocus) {
196                    mFocusViewAnimator = updateSelector(mSelectorView, view, mFocusViewAnimator,
197                            true);
198                }
199            });
200            mMediaItemNumberViewFlipper =
201                    (ViewFlipper) view.findViewById(R.id.mediaItemNumberViewFlipper);
202
203            TypedValue typedValue = new TypedValue();
204            boolean found = view.getContext().getTheme().resolveAttribute(
205                    R.attr.playbackMediaItemNumberViewFlipperLayout, typedValue, true);
206            View mergeView = LayoutInflater.from(view.getContext()).
207                    inflate(found ? typedValue.resourceId :
208                            R.layout.lb_media_item_number_view_flipper,
209                            mMediaItemNumberViewFlipper, true);
210
211            mMediaItemNumberView = (TextView) mergeView.findViewById(R.id.initial);
212            mMediaItemPausedView = mergeView.findViewById(R.id.paused);
213            mMediaItemPlayingView = mergeView.findViewById(R.id.playing);
214        }
215
216        /**
217         * Binds the actions in a media item row object to their views. This consists of creating
218         * (or reusing the existing) action view holders, and populating them with the actions'
219         * icons.
220         */
221        public void onBindRowActions() {
222            for (int i = getMediaItemActionsContainer().getChildCount() - 1;
223                 i >= mActionViewHolders.size(); i--) {
224                getMediaItemActionsContainer().removeViewAt(i);
225                mActionViewHolders.remove(i);
226            }
227            mMediaItemRowActions = null;
228
229            Object rowObject = getRowObject();
230            final MultiActionsProvider.MultiAction[] actionList;
231            if (rowObject instanceof MultiActionsProvider) {
232                actionList = ((MultiActionsProvider) rowObject).getActions();
233            } else {
234                return;
235            }
236            Presenter actionPresenter = mRowPresenter.getActionPresenter();
237            if (actionPresenter == null) {
238                return;
239            }
240
241            mMediaItemRowActions = actionList;
242            for (int i = mActionViewHolders.size(); i < actionList.length; i++) {
243                final int actionIndex = i;
244                final Presenter.ViewHolder actionViewHolder = actionPresenter.
245                        onCreateViewHolder(getMediaItemActionsContainer());
246                getMediaItemActionsContainer().addView(actionViewHolder.view);
247                mActionViewHolders.add(actionViewHolder);
248                actionViewHolder.view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
249                    @Override
250                    public void onFocusChange(View view, boolean hasFocus) {
251                        mFocusViewAnimator = updateSelector(mSelectorView, view,
252                                mFocusViewAnimator, false);
253                    }
254                });
255                actionViewHolder.view.setOnClickListener(
256                        new View.OnClickListener() {
257                            @Override
258                            public void onClick(View view) {
259                                if (getOnItemViewClickedListener() != null) {
260                                    getOnItemViewClickedListener().onItemClicked(
261                                            actionViewHolder, mMediaItemRowActions[actionIndex],
262                                            ViewHolder.this, getRowObject());
263                                }
264                            }
265                        });
266            }
267
268            if (mMediaItemActionsContainer != null) {
269                for (int i = 0; i < actionList.length; i++) {
270                    Presenter.ViewHolder avh = mActionViewHolders.get(i);
271                    actionPresenter.onUnbindViewHolder(avh);
272                    actionPresenter.onBindViewHolder(avh, mMediaItemRowActions[i]);
273                }
274            }
275
276        }
277
278        int findActionIndex(MultiActionsProvider.MultiAction action) {
279            if (mMediaItemRowActions != null) {
280                for (int i = 0; i < mMediaItemRowActions.length; i++) {
281                    if (mMediaItemRowActions[i] == action) {
282                        return i;
283                    }
284                }
285            }
286            return -1;
287        }
288
289        /**
290         * Notifies an action has changed in this media row and the UI needs to be updated
291         * @param action The action whose state has changed
292         */
293        public void notifyActionChanged(MultiActionsProvider.MultiAction action) {
294            Presenter actionPresenter = mRowPresenter.getActionPresenter();
295            if (actionPresenter == null) {
296                return;
297            }
298            int actionIndex = findActionIndex(action);
299            if (actionIndex >= 0) {
300                Presenter.ViewHolder actionViewHolder = mActionViewHolders.get(actionIndex);
301                actionPresenter.onUnbindViewHolder(actionViewHolder);
302                actionPresenter.onBindViewHolder(actionViewHolder, action);
303            }
304        }
305
306        /**
307         * Notifies the content of the media item details in a row has changed and triggers updating
308         * the UI. This causes {@link #onBindMediaDetails(ViewHolder, Object)}
309         * on the user's provided presenter to be called back, allowing them to update UI
310         * accordingly.
311         */
312        public void notifyDetailsChanged() {
313            mRowPresenter.onUnbindMediaDetails(this);
314            mRowPresenter.onBindMediaDetails(this, getRowObject());
315        }
316
317        /**
318         * Notifies the playback state of the media item row has changed. This in turn triggers
319         * updating of the UI for that media item row if corresponding views are specified for each
320         * playback state.
321         * By default, 3 views are provided for each playback state, or these views can be provided
322         * by the user.
323         */
324        public void notifyPlayStateChanged() {
325            mRowPresenter.onBindMediaPlayState(this);
326        }
327
328        /**
329         * @return The SelectorView responsible for highlighting the in-focus view within each
330         * media item row
331         */
332        public View getSelectorView() {
333            return mSelectorView;
334        }
335
336        /**
337         * @return The FlipperView responsible for flipping between different media item number
338         * views depending on the playback state
339         */
340        public ViewFlipper getMediaItemNumberViewFlipper() {
341            return mMediaItemNumberViewFlipper;
342        }
343
344        /**
345         * @return The TextView responsible for rendering the media item number.
346         * This view is rendered when the media item row is neither playing nor paused.
347         */
348        public TextView getMediaItemNumberView() {
349            return mMediaItemNumberView;
350        }
351
352        /**
353         * @return The view rendered when the media item row is paused.
354         */
355        public View getMediaItemPausedView() {
356            return mMediaItemPausedView;
357        }
358
359        /**
360         * @return The view rendered when the media item row is playing.
361         */
362        public View getMediaItemPlayingView() {
363            return mMediaItemPlayingView;
364        }
365
366
367        /**
368         * Flips to the view at index 'position'. This position corresponds to the index of a
369         * particular view within the ViewFlipper layout specified for the MediaItemNumberView
370         * (see playbackMediaItemNumberViewFlipperLayout attribute).
371         * @param position The index of the child view to display.
372         */
373        public void setSelectedMediaItemNumberView(int position) {
374            if (position >= 0 & position < mMediaItemNumberViewFlipper.getChildCount()) {
375                mMediaItemNumberViewFlipper.setDisplayedChild(position);
376            }
377        }
378        /**
379         * Returns the view displayed when the media item is neither playing nor paused,
380         * corresponding to the playback state of PLAY_STATE_INITIAL.
381         * @return The TextView responsible for rendering the media item name.
382         */
383        public TextView getMediaItemNameView() {
384            return mMediaItemNameView;
385        }
386
387        /**
388         * @return The TextView responsible for rendering the media item duration
389         */
390        public TextView getMediaItemDurationView() {
391            return mMediaItemDurationView;
392        }
393
394        /**
395         * @return The view container of media item details
396         */
397        public View getMediaItemDetailsView() {
398            return mMediaItemDetailsView;
399        }
400
401        /**
402         * @return The view responsible for rendering the separator line between media rows
403         */
404        public View getMediaItemRowSeparator() {
405            return mMediaItemRowSeparator;
406        }
407
408        /**
409         * @return The view containing the set of custom actions
410         */
411        public ViewGroup getMediaItemActionsContainer() {
412            return mMediaItemActionsContainer;
413        }
414
415        /**
416         * @return Array of MultiActions displayed for this media item row
417         */
418        public MultiActionsProvider.MultiAction[] getMediaItemRowActions() {
419            return mMediaItemRowActions;
420        }
421    }
422
423    @Override
424    protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
425        Context context = parent.getContext();
426        if (mThemeId != 0) {
427            context = new ContextThemeWrapper(context, mThemeId);
428        }
429        View view = LayoutInflater.from(context).
430                inflate(R.layout.lb_row_media_item, parent, false);
431        final ViewHolder vh = new ViewHolder(view);
432        vh.mRowPresenter = this;
433        if (mBackgroundColorSet) {
434            vh.mMediaRowView.setBackgroundColor(mBackgroundColor);
435        }
436        return vh;
437    }
438
439    @Override
440    public boolean isUsingDefaultSelectEffect() {
441        return false;
442    }
443
444    @Override
445    protected boolean isClippingChildren() {
446        return true;
447    }
448
449    @Override
450    protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
451        super.onBindRowViewHolder(vh, item);
452
453        final ViewHolder mvh = (ViewHolder) vh;
454
455        onBindRowActions(mvh);
456
457        mvh.getMediaItemRowSeparator().setVisibility(hasMediaRowSeparator() ? View.VISIBLE :
458                View.GONE);
459
460        onBindMediaPlayState(mvh);
461        onBindMediaDetails((ViewHolder) vh, item);
462    }
463
464    /**
465     * Binds the given media item object action to the given ViewHolder's action views.
466     * @param vh ViewHolder for the media item.
467     */
468    protected void onBindRowActions(ViewHolder vh) {
469        vh.onBindRowActions();
470    }
471
472    /**
473     * Sets the background color for the row views within the playlist.
474     * If this is not set, a default color, defaultBrandColor, from theme is used.
475     * This defaultBrandColor defaults to android:attr/colorPrimary on v21, if it's specified.
476     * @param color The ARGB color used to set as the media list background color.
477     */
478    public void setBackgroundColor(int color) {
479        mBackgroundColorSet = true;
480        mBackgroundColor = color;
481    }
482
483    /**
484     * Specifies whether a line separator should be used between media item rows.
485     * @param hasSeparator true if a separator should be displayed, false otherwise.
486     */
487    public void setHasMediaRowSeparator(boolean hasSeparator) {
488        mMediaRowSeparator = hasSeparator;
489    }
490
491    public boolean hasMediaRowSeparator() {
492        return mMediaRowSeparator;
493    }
494    /**
495     * Binds the media item details to their views provided by the
496     * {@link AbstractMediaItemPresenter}.
497     * This method is to be overridden by the users of this presenter.
498     * The subclasses of this presenter can access and bind individual views for either of the
499     * media item number, name, or duration (depending on whichever views are visible according to
500     * the providing theme attributes), by calling {@link ViewHolder#getMediaItemNumberView()},
501     * {@link ViewHolder#getMediaItemNameView()}, and {@link ViewHolder#getMediaItemDurationView()},
502     * on the {@link ViewHolder} provided as the argument {@code vh} of this presenter.
503     *
504     * @param vh The ViewHolder for this {@link AbstractMediaItemPresenter}.
505     * @param item The media item row object being presented.
506     */
507    protected abstract void onBindMediaDetails(ViewHolder vh, Object item);
508
509    /**
510     * Unbinds the media item details from their views provided by the
511     * {@link AbstractMediaItemPresenter}.
512     * This method can be overridden by the subclasses of this presenter if required.
513     * @param vh ViewHolder to unbind from.
514     */
515    protected void onUnbindMediaDetails(ViewHolder vh) {
516    }
517
518    /**
519     * Binds the media item number view to the appropriate play state view of the media item.
520     * The play state of the media item is extracted by calling {@link #getMediaPlayState(Object)} for
521     * the media item embedded within this view.
522     * This method triggers updating of the playback state UI if corresponding views are specified
523     * for the current playback state.
524     * By default, 3 views are provided for each playback state, or these views can be provided
525     * by the user.
526     */
527    public void onBindMediaPlayState(ViewHolder vh) {
528        int childIndex = calculateMediaItemNumberFlipperIndex(vh);
529        if (childIndex != -1 && vh.mMediaItemNumberViewFlipper.getDisplayedChild() != childIndex) {
530            vh.mMediaItemNumberViewFlipper.setDisplayedChild(childIndex);
531        }
532    }
533
534    static int calculateMediaItemNumberFlipperIndex(ViewHolder vh) {
535        int childIndex = -1;
536        int newPlayState = vh.mRowPresenter.getMediaPlayState(vh.getRowObject());
537        switch (newPlayState) {
538            case PLAY_STATE_INITIAL:
539                childIndex = (vh.mMediaItemNumberView == null) ? -1 :
540                        vh.mMediaItemNumberViewFlipper.indexOfChild(vh.mMediaItemNumberView);
541                break;
542            case PLAY_STATE_PAUSED:
543                childIndex = (vh.mMediaItemPausedView == null) ? -1 :
544                        vh.mMediaItemNumberViewFlipper.indexOfChild(vh.mMediaItemPausedView);
545                break;
546            case PLAY_STATE_PLAYING:
547                childIndex = (vh.mMediaItemPlayingView == null) ? -1 :
548                        vh.mMediaItemNumberViewFlipper.indexOfChild(vh.mMediaItemPlayingView);
549        }
550        return childIndex;
551    }
552
553    /**
554     * Called when the given ViewHolder wants to unbind the play state view.
555     * @param vh The ViewHolder to unbind from.
556     */
557    public void onUnbindMediaPlayState(ViewHolder vh) {
558    }
559
560    /**
561     * Returns the current play state of the given media item. By default, this method returns
562     * PLAY_STATE_INITIAL which causes the media item number
563     * {@link ViewHolder#getMediaItemNameView()} to be displayed for different
564     * playback states. Users of this class should override this method in order to provide the
565     * play state of their custom media item data model.
566     * @param item The media item
567     * @return The current play state of this media item
568     */
569    protected int getMediaPlayState(Object item) {
570        return PLAY_STATE_INITIAL;
571    }
572    /**
573     * Each media item row can have multiple focusable elements; the details on the left and a set
574     * of optional custom actions on the right.
575     * The selector is a highlight that moves to highlight to cover whichever views is in focus.
576     *
577     * @param selectorView the selector view used to highlight an individual element within a row.
578     * @param focusChangedView The component within the media row whose focus got changed.
579     * @param layoutAnimator the ValueAnimator producing animation frames for the selector's width
580     *                       and x-translation, generated by this method and stored for the each
581     *                       {@link ViewHolder}.
582     * @param isDetails Whether the changed-focused view is for a media item details (true) or
583     *                  an action (false).
584     */
585    private static ValueAnimator updateSelector(final View selectorView,
586            View focusChangedView, ValueAnimator layoutAnimator, boolean isDetails) {
587        int animationDuration = focusChangedView.getContext().getResources()
588                .getInteger(android.R.integer.config_shortAnimTime);
589        DecelerateInterpolator interpolator = new DecelerateInterpolator();
590
591        int layoutDirection = ViewCompat.getLayoutDirection(selectorView);
592        if (!focusChangedView.hasFocus()) {
593            // if neither of the details or action views are in focus (ie. another row is in focus),
594            // animate the selector out.
595            selectorView.animate().cancel();
596            selectorView.animate().alpha(0f).setDuration(animationDuration)
597                    .setInterpolator(interpolator).start();
598            // keep existing layout animator
599            return layoutAnimator;
600        } else {
601            // cancel existing layout animator
602            if (layoutAnimator != null) {
603                layoutAnimator.cancel();
604                layoutAnimator = null;
605            }
606            float currentAlpha = selectorView.getAlpha();
607            selectorView.animate().alpha(1f).setDuration(animationDuration)
608                    .setInterpolator(interpolator).start();
609
610            final ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)
611                    selectorView.getLayoutParams();
612            ViewGroup rootView = (ViewGroup) selectorView.getParent();
613            sTempRect.set(0, 0, focusChangedView.getWidth(), focusChangedView.getHeight());
614            rootView.offsetDescendantRectToMyCoords(focusChangedView, sTempRect);
615            if (isDetails) {
616                if (layoutDirection == View.LAYOUT_DIRECTION_RTL ) {
617                    sTempRect.right += rootView.getHeight();
618                    sTempRect.left -= rootView.getHeight() / 2;
619                } else {
620                    sTempRect.left -= rootView.getHeight();
621                    sTempRect.right += rootView.getHeight() / 2;
622                }
623            }
624            final int targetLeft = sTempRect.left;
625            final int targetWidth = sTempRect.width();
626            final float deltaWidth = lp.width - targetWidth;
627            final float deltaLeft = lp.leftMargin - targetLeft;
628
629            if (deltaLeft == 0f && deltaWidth == 0f)
630            {
631                // no change needed
632            } else if (currentAlpha == 0f) {
633                // change selector to the proper width and marginLeft without animation.
634                lp.width = targetWidth;
635                lp.leftMargin = targetLeft;
636                selectorView.requestLayout();
637            } else {
638                // animate the selector to the proper width and marginLeft.
639                layoutAnimator = ValueAnimator.ofFloat(0f, 1f);
640                layoutAnimator.setDuration(animationDuration);
641                layoutAnimator.setInterpolator(interpolator);
642
643                layoutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
644                    @Override
645                    public void onAnimationUpdate(ValueAnimator valueAnimator) {
646                        // Set width to the proper width for this animation step.
647                        float fractionToEnd = 1f - valueAnimator.getAnimatedFraction();
648                        lp.leftMargin = Math.round(targetLeft + deltaLeft * fractionToEnd);
649                        lp.width = Math.round(targetWidth + deltaWidth * fractionToEnd);
650                        selectorView.requestLayout();
651                    }
652                });
653                layoutAnimator.start();
654            }
655            return layoutAnimator;
656
657        }
658    }
659}
660