1d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/* 2d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Copyright (C) 2015 The Android Open Source Project 3d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 4d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Licensed under the Apache License, Version 2.0 (the "License"); 5d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * you may not use this file except in compliance with the License. 6d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * You may obtain a copy of the License at 7d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 8d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * http://www.apache.org/licenses/LICENSE-2.0 9d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 10d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Unless required by applicable law or agreed to in writing, software 11d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * distributed under the License is distributed on an "AS IS" BASIS, 12d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * See the License for the specific language governing permissions and 14d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * limitations under the License. 15d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 16d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 17d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpackage com.android.messaging.ui.conversation; 18d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 19d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.animation.AnimatorSet; 20d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.animation.ObjectAnimator; 21d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.content.Context; 22d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.content.res.Resources; 23d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.graphics.Rect; 24d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.graphics.drawable.StateListDrawable; 25d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.os.Handler; 26d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.support.v7.widget.LinearLayoutManager; 27d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.support.v7.widget.RecyclerView; 28d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.support.v7.widget.RecyclerView.AdapterDataObserver; 29d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.support.v7.widget.RecyclerView.ViewHolder; 30d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.util.StateSet; 31d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.LayoutInflater; 32d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.MotionEvent; 33d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.View; 34d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.View.MeasureSpec; 35d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.View.OnLayoutChangeListener; 36d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.ViewGroupOverlay; 37d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.widget.ImageView; 38d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.widget.TextView; 39d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 40d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.R; 41d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.data.ConversationMessageData; 42d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.ui.ConversationDrawables; 43d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.Dates; 44d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.OsUtil; 45d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 46d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/** 47d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Adds a "fast-scroll" bar to the conversation RecyclerView that shows the current position within 48d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * the conversation and allows quickly moving to another position by dragging the scrollbar thumb 49d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * up or down. As the thumb is dragged, we show a floating bubble alongside it that shows the 50d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * date/time of the first visible message at the current position. 51d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 52d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpublic class ConversationFastScroller extends RecyclerView.OnScrollListener implements 53d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd OnLayoutChangeListener, RecyclerView.OnItemTouchListener { 54d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 55d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 56d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Creates a {@link ConversationFastScroller} instance, attached to the provided 57d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * {@link RecyclerView}. 58d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * 59d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @param rv the conversation RecyclerView 60d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @param position where the scrollbar should appear (either {@code POSITION_RIGHT_SIDE} or 61d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * {@code POSITION_LEFT_SIDE}) 62d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * @return a new ConversationFastScroller, or {@code null} if fast-scrolling is not supported 63d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * (the feature requires Jellybean MR2 or newer) 64d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 65d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public static ConversationFastScroller addTo(RecyclerView rv, int position) { 66d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (OsUtil.isAtLeastJB_MR2()) { 67d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return new ConversationFastScroller(rv, position); 68d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 69d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return null; 70d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 71d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 72d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public static final int POSITION_RIGHT_SIDE = 0; 73d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public static final int POSITION_LEFT_SIDE = 1; 74d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 75d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static final int MIN_PAGES_TO_ENABLE = 7; 76d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static final int SHOW_ANIMATION_DURATION_MS = 150; 77d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static final int HIDE_ANIMATION_DURATION_MS = 300; 78d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private static final int HIDE_DELAY_MS = 1500; 79d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 80d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final Context mContext; 81d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final RecyclerView mRv; 82d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final ViewGroupOverlay mOverlay; 83d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final ImageView mTrackImageView; 84d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final ImageView mThumbImageView; 85d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final TextView mPreviewTextView; 86d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 87d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final int mTrackWidth; 88d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final int mThumbHeight; 89d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final int mPreviewHeight; 90d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final int mPreviewMinWidth; 91d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final int mPreviewMarginTop; 92d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final int mPreviewMarginLeftRight; 93d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final int mTouchSlop; 94d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 95d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final Rect mContainer = new Rect(); 96d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final Handler mHandler = new Handler(); 97d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 98d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Whether to render the scrollbar on the right side (otherwise it'll be on the left). 99d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final boolean mPosRight; 100d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 101d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Whether the scrollbar is currently visible (it may still be animating). 102d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private boolean mVisible = false; 103d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 104d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Whether we are waiting to hide the scrollbar (i.e. scrolling has stopped). 105d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private boolean mPendingHide = false; 106d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 107d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Whether the user is currently dragging the thumb up or down. 108d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private boolean mDragging = false; 109d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 110d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Animations responsible for hiding the scrollbar & preview. May be null. 111d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private AnimatorSet mHideAnimation; 112d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private ObjectAnimator mHidePreviewAnimation; 113d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 114d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private final Runnable mHideTrackRunnable = new Runnable() { 115d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 116d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void run() { 117d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd hide(true /* animate */); 118d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPendingHide = false; 119d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 120d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd }; 121d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 122d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private ConversationFastScroller(RecyclerView rv, int position) { 123d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mContext = rv.getContext(); 124d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mRv = rv; 125d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mRv.addOnLayoutChangeListener(this); 126d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mRv.addOnScrollListener(this); 127d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mRv.addOnItemTouchListener(this); 128d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mRv.getAdapter().registerAdapterDataObserver(new AdapterDataObserver() { 129d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 130d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void onChanged() { 131d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd updateScrollPos(); 132d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 133d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd }); 134d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPosRight = (position == POSITION_RIGHT_SIDE); 135d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 136d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Cache the dimensions we'll need during layout 137d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final Resources res = mContext.getResources(); 138d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mTrackWidth = res.getDimensionPixelSize(R.dimen.fastscroll_track_width); 139d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mThumbHeight = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_height); 140d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewHeight = res.getDimensionPixelSize(R.dimen.fastscroll_preview_height); 141d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewMinWidth = res.getDimensionPixelSize(R.dimen.fastscroll_preview_min_width); 142d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewMarginTop = res.getDimensionPixelOffset(R.dimen.fastscroll_preview_margin_top); 143d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewMarginLeftRight = res.getDimensionPixelOffset( 144d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd R.dimen.fastscroll_preview_margin_left_right); 145d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mTouchSlop = res.getDimensionPixelOffset(R.dimen.fastscroll_touch_slop); 146d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 147d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final LayoutInflater inflator = LayoutInflater.from(mContext); 148d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mTrackImageView = (ImageView) inflator.inflate(R.layout.fastscroll_track, null); 149d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mThumbImageView = (ImageView) inflator.inflate(R.layout.fastscroll_thumb, null); 150d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewTextView = (TextView) inflator.inflate(R.layout.fastscroll_preview, null); 151d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 152d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd refreshConversationThemeColor(); 153d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 154d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Add the fast scroll views to the overlay, so they are rendered above the list 155d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mOverlay = rv.getOverlay(); 156d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mOverlay.add(mTrackImageView); 157d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mOverlay.add(mThumbImageView); 158d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mOverlay.add(mPreviewTextView); 159d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 160d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd hide(false /* animate */); 161d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewTextView.setAlpha(0f); 162d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 163d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 164d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void refreshConversationThemeColor() { 165d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewTextView.setBackground( 166d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ConversationDrawables.get().getFastScrollPreviewDrawable(mPosRight)); 167d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (OsUtil.isAtLeastL()) { 168d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final StateListDrawable drawable = new StateListDrawable(); 169d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd drawable.addState(new int[]{ android.R.attr.state_pressed }, 170d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ConversationDrawables.get().getFastScrollThumbDrawable(true /* pressed */)); 171d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd drawable.addState(StateSet.WILD_CARD, 172d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ConversationDrawables.get().getFastScrollThumbDrawable(false /* pressed */)); 173d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mThumbImageView.setImageDrawable(drawable); 174d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else { 175d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Android pre-L doesn't seem to handle a StateListDrawable containing a tinted 176d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // drawable (it's rendered in the filter base color, which is red), so fall back to 177d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // just the regular (non-pressed) drawable. 178d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mThumbImageView.setImageDrawable( 179d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ConversationDrawables.get().getFastScrollThumbDrawable(false /* pressed */)); 180d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 181d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 182d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 183d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 184d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void onScrollStateChanged(final RecyclerView view, final int newState) { 185d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { 186d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Only show the scrollbar once the user starts scrolling 187d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (!mVisible && isEnabled()) { 188d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd show(); 189d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 190d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd cancelAnyPendingHide(); 191d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else if (newState == RecyclerView.SCROLL_STATE_IDLE && !mDragging) { 192d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Hide the scrollbar again after scrolling stops 193d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd hideAfterDelay(); 194d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 195d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 196d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 197d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private boolean isEnabled() { 198d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int range = mRv.computeVerticalScrollRange(); 199d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int extent = mRv.computeVerticalScrollExtent(); 200d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 201d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (range == 0 || extent == 0) { 202d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return false; // Conversation isn't long enough to scroll 203d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 204d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Only enable scrollbars for conversations long enough that they would require several 205d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // flings to scroll through. 206d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final float pages = (float) range / extent; 207d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return (pages > MIN_PAGES_TO_ENABLE); 208d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 209d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 210d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void show() { 211d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mHideAnimation != null && mHideAnimation.isRunning()) { 212d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mHideAnimation.cancel(); 213d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 214d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Slide the scrollbar in from the side 215d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ObjectAnimator trackSlide = ObjectAnimator.ofFloat(mTrackImageView, View.TRANSLATION_X, 0); 216d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ObjectAnimator thumbSlide = ObjectAnimator.ofFloat(mThumbImageView, View.TRANSLATION_X, 0); 217d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd AnimatorSet animation = new AnimatorSet(); 218d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd animation.playTogether(trackSlide, thumbSlide); 219d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd animation.setDuration(SHOW_ANIMATION_DURATION_MS); 220d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd animation.start(); 221d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 222d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mVisible = true; 223d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd updateScrollPos(); 224d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 225d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 226d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void hideAfterDelay() { 227d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd cancelAnyPendingHide(); 228d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mHandler.postDelayed(mHideTrackRunnable, HIDE_DELAY_MS); 229d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPendingHide = true; 230d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 231d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 232d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void cancelAnyPendingHide() { 233d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mPendingHide) { 234d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mHandler.removeCallbacks(mHideTrackRunnable); 235d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 236d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 237d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 238d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void hide(boolean animate) { 239d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int hiddenTranslationX = mPosRight ? mTrackWidth : -mTrackWidth; 240d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (animate) { 241d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Slide the scrollbar off to the side 242d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ObjectAnimator trackSlide = ObjectAnimator.ofFloat(mTrackImageView, View.TRANSLATION_X, 243d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd hiddenTranslationX); 244d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd ObjectAnimator thumbSlide = ObjectAnimator.ofFloat(mThumbImageView, View.TRANSLATION_X, 245d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd hiddenTranslationX); 246d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mHideAnimation = new AnimatorSet(); 247d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mHideAnimation.playTogether(trackSlide, thumbSlide); 248d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mHideAnimation.setDuration(HIDE_ANIMATION_DURATION_MS); 249d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mHideAnimation.start(); 250d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else { 251d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mTrackImageView.setTranslationX(hiddenTranslationX); 252d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mThumbImageView.setTranslationX(hiddenTranslationX); 253d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 254d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 255d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mVisible = false; 256d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 257d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 258d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void showPreview() { 259d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mHidePreviewAnimation != null && mHidePreviewAnimation.isRunning()) { 260d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mHidePreviewAnimation.cancel(); 261d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 262d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewTextView.setAlpha(1f); 263d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 264d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 265d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void hidePreview() { 266d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mHidePreviewAnimation = ObjectAnimator.ofFloat(mPreviewTextView, View.ALPHA, 0f); 267d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mHidePreviewAnimation.setDuration(HIDE_ANIMATION_DURATION_MS); 268d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mHidePreviewAnimation.start(); 269d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 270d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 271d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 272d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void onScrolled(final RecyclerView view, final int dx, final int dy) { 273d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd updateScrollPos(); 274d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 275d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 276d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void updateScrollPos() { 277d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (!mVisible) { 278d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return; 279d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 280d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int verticalScrollLength = mContainer.height() - mThumbHeight; 281d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int verticalScrollStart = mContainer.top + mThumbHeight / 2; 282d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 283d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final float scrollRatio = computeScrollRatio(); 284d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int thumbCenterY = verticalScrollStart + (int)(verticalScrollLength * scrollRatio); 285d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd layoutThumb(thumbCenterY); 286d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 287d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mDragging) { 288d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd updatePreviewText(); 289d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd layoutPreview(thumbCenterY); 290d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 291d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 292d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 293d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd /** 294d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Returns the current position in the conversation, as a value between 0 and 1, inclusive. 295d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * The top of the conversation is 0, the bottom is 1, the exact middle is 0.5, and so on. 296d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */ 297d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private float computeScrollRatio() { 298d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int range = mRv.computeVerticalScrollRange(); 299d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int extent = mRv.computeVerticalScrollExtent(); 300d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int offset = mRv.computeVerticalScrollOffset(); 301d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 302d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (range == 0 || extent == 0) { 303d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // If the conversation doesn't scroll, we're at the bottom. 304d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return 1.0f; 305d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 306d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int scrollRange = range - extent; 307d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd offset = Math.min(offset, scrollRange); 308d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return offset / (float) scrollRange; 309d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 310d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 311d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void updatePreviewText() { 312d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final LinearLayoutManager lm = (LinearLayoutManager) mRv.getLayoutManager(); 313d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int pos = lm.findFirstVisibleItemPosition(); 314d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (pos == RecyclerView.NO_POSITION) { 315d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return; 316d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 317d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final ViewHolder vh = mRv.findViewHolderForAdapterPosition(pos); 318d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (vh == null) { 319d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // This can happen if the messages update while we're dragging the thumb. 320d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return; 321d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 322d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final ConversationMessageView messageView = (ConversationMessageView) vh.itemView; 323d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final ConversationMessageData messageData = messageView.getData(); 324d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final long timestamp = messageData.getReceivedTimeStamp(); 325d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final CharSequence timestampText = Dates.getFastScrollPreviewTimeString(timestamp); 326d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewTextView.setText(timestampText); 327d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 328d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 329d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 330d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { 331d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (!mVisible) { 332d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return false; 333d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 334d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // If the user presses down on the scroll thumb, we'll start intercepting events from the 335d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // RecyclerView so we can handle the move events while they're dragging it up/down. 336d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int action = e.getActionMasked(); 337d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd switch (action) { 338d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd case MotionEvent.ACTION_DOWN: 339d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (isInsideThumb(e.getX(), e.getY())) { 340d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd startDrag(); 341d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return true; 342d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 343d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd break; 344d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd case MotionEvent.ACTION_MOVE: 345d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mDragging) { 346d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return true; 347d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 348d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd case MotionEvent.ACTION_CANCEL: 349d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd case MotionEvent.ACTION_UP: 350d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mDragging) { 351d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd cancelDrag(); 352d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 353d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return false; 354d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 355d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return false; 356d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 357d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 358d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private boolean isInsideThumb(float x, float y) { 359d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int hitTargetLeft = mThumbImageView.getLeft() - mTouchSlop; 360d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int hitTargetRight = mThumbImageView.getRight() + mTouchSlop; 361d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 362d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (x < hitTargetLeft || x > hitTargetRight) { 363d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return false; 364d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 365d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (y < mThumbImageView.getTop() || y > mThumbImageView.getBottom()) { 366d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return false; 367d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 368d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return true; 369d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 370d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 371d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 372d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void onTouchEvent(RecyclerView rv, MotionEvent e) { 373d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (!mDragging) { 374d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd return; 375d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 376d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int action = e.getActionMasked(); 377d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd switch (action) { 378d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd case MotionEvent.ACTION_MOVE: 379d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd handleDragMove(e.getY()); 380d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd break; 381d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd case MotionEvent.ACTION_CANCEL: 382d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd case MotionEvent.ACTION_UP: 383d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd cancelDrag(); 384d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd break; 385d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 386d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 387d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 388d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 389d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { 390d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 391d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 392d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void startDrag() { 393d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mDragging = true; 394d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mThumbImageView.setPressed(true); 395d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd updateScrollPos(); 396d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd showPreview(); 397d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd cancelAnyPendingHide(); 398d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 399d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 400d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void handleDragMove(float y) { 401d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int verticalScrollLength = mContainer.height() - mThumbHeight; 402d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int verticalScrollStart = mContainer.top + (mThumbHeight / 2); 403d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 404d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Convert the desired position from px to a scroll position in the conversation. 405d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd float dragScrollRatio = (y - verticalScrollStart) / verticalScrollLength; 406d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd dragScrollRatio = Math.max(dragScrollRatio, 0.0f); 407d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd dragScrollRatio = Math.min(dragScrollRatio, 1.0f); 408d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 409d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Scroll the RecyclerView to a new position. 410d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int itemCount = mRv.getAdapter().getItemCount(); 411d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int itemPos = (int)((itemCount - 1) * dragScrollRatio); 412d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mRv.scrollToPosition(itemPos); 413d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 414d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 415d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void cancelDrag() { 416d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mDragging = false; 417d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mThumbImageView.setPressed(false); 418d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd hidePreview(); 419d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd hideAfterDelay(); 420d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 421d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 422d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd @Override 423d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd public void onLayoutChange(View v, int left, int top, int right, int bottom, 424d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int oldLeft, int oldTop, int oldRight, int oldBottom) { 425d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (!mVisible) { 426d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd hide(false /* animate */); 427d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 428d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // The container is the size of the RecyclerView that's visible on screen. We have to 429d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // exclude the top padding, because it's usually hidden behind the conversation action bar. 430d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mContainer.set(left, top + mRv.getPaddingTop(), right, bottom); 431d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd layoutTrack(); 432d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd updateScrollPos(); 433d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 434d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 435d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void layoutTrack() { 436d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int trackHeight = Math.max(0, mContainer.height()); 437d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTrackWidth, MeasureSpec.EXACTLY); 438d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int heightMeasureSpec = MeasureSpec.makeMeasureSpec(trackHeight, MeasureSpec.EXACTLY); 439d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mTrackImageView.measure(widthMeasureSpec, heightMeasureSpec); 440d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 441d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int left = mPosRight ? (mContainer.right - mTrackWidth) : mContainer.left; 442d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int top = mContainer.top; 443d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int right = mPosRight ? mContainer.right : (mContainer.left + mTrackWidth); 444d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int bottom = mContainer.bottom; 445d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mTrackImageView.layout(left, top, right, bottom); 446d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 447d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 448d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void layoutThumb(int centerY) { 449d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTrackWidth, MeasureSpec.EXACTLY); 450d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int heightMeasureSpec = MeasureSpec.makeMeasureSpec(mThumbHeight, MeasureSpec.EXACTLY); 451d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mThumbImageView.measure(widthMeasureSpec, heightMeasureSpec); 452d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 453d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int left = mPosRight ? (mContainer.right - mTrackWidth) : mContainer.left; 454d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int top = centerY - (mThumbImageView.getHeight() / 2); 455d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int right = mPosRight ? mContainer.right : (mContainer.left + mTrackWidth); 456d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int bottom = top + mThumbHeight; 457d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mThumbImageView.layout(left, top, right, bottom); 458d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 459d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 460d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd private void layoutPreview(int centerY) { 461d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mContainer.width(), MeasureSpec.AT_MOST); 462d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int heightMeasureSpec = MeasureSpec.makeMeasureSpec(mPreviewHeight, MeasureSpec.EXACTLY); 463d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewTextView.measure(widthMeasureSpec, heightMeasureSpec); 464d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 465d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd // Ensure that the preview bubble is at least as wide as it is tall 466d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mPreviewTextView.getMeasuredWidth() < mPreviewMinWidth) { 467d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd widthMeasureSpec = MeasureSpec.makeMeasureSpec(mPreviewMinWidth, MeasureSpec.EXACTLY); 468d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewTextView.measure(widthMeasureSpec, heightMeasureSpec); 469d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 470d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int previewMinY = mContainer.top + mPreviewMarginTop; 471d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 472d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd final int left, right; 473d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (mPosRight) { 474d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd right = mContainer.right - mTrackWidth - mPreviewMarginLeftRight; 475d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd left = right - mPreviewTextView.getMeasuredWidth(); 476d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } else { 477d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd left = mContainer.left + mTrackWidth + mPreviewMarginLeftRight; 478d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd right = left + mPreviewTextView.getMeasuredWidth(); 479d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 480d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd 481d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int bottom = centerY; 482d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd int top = bottom - mPreviewTextView.getMeasuredHeight(); 483d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd if (top < previewMinY) { 484d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd top = previewMinY; 485d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd bottom = top + mPreviewTextView.getMeasuredHeight(); 486d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 487d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd mPreviewTextView.layout(left, top, right, bottom); 488d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd } 489d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd} 490