diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/base/MediaEntryViewHolder.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/base/MediaEntryViewHolder.java index 8ad2a3f10..45d4ffb49 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/base/MediaEntryViewHolder.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/base/MediaEntryViewHolder.java @@ -6,9 +6,11 @@ import android.support.v7.widget.RecyclerView; import android.view.View; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.TextView; import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.views.TouchInterceptHorizontalScrollView; import butterknife.BindView; import butterknife.ButterKnife; @@ -17,6 +19,10 @@ * @author Karim Abou Zeid (kabouzeid) */ public class MediaEntryViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { + @Nullable + @BindView(R.id.song_view) + public LinearLayout songView; + @Nullable @BindView(R.id.image) public ImageView image; @@ -29,6 +35,10 @@ public class MediaEntryViewHolder extends RecyclerView.ViewHolder implements Vie @BindView(R.id.title) public TextView title; + @Nullable + @BindView(R.id.title_scrollview) + public TouchInterceptHorizontalScrollView title_scrollview; + @Nullable @BindView(R.id.text) public TextView text; @@ -74,6 +84,5 @@ public boolean onLongClick(View v) { @Override public void onClick(View v) { - } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/song/SongAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/song/SongAdapter.java index ea1094469..40885a551 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/song/SongAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/song/SongAdapter.java @@ -8,6 +8,7 @@ import android.support.v7.app.AppCompatActivity; import android.view.LayoutInflater; import android.view.MenuItem; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -115,7 +116,6 @@ public void onBindViewHolder(@NonNull final ViewHolder holder, int position) { } loadAlbumCover(song, holder); - } private void setColors(int color, ViewHolder holder) { @@ -197,6 +197,12 @@ public ViewHolder(@NonNull View itemView) { if (menu == null) { return; } + menu.setOnTouchListener(new View.OnTouchListener() { + public boolean onTouch(View v, MotionEvent ev) { + menu.getParent().requestDisallowInterceptTouchEvent(true); + return false; + } + }); menu.setOnClickListener(new SongMenuHelper.OnClickSongMenu(activity) { @Override public Song getSong() { diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AlbumDetailActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AlbumDetailActivity.java index f435d5058..d4f68d2f6 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AlbumDetailActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/AlbumDetailActivity.java @@ -17,6 +17,7 @@ import android.view.MenuItem; import android.view.View; import android.widget.ImageView; +import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; @@ -55,9 +56,8 @@ import com.kabouzeid.gramophone.util.PhonographColorUtil; import com.kabouzeid.gramophone.util.Util; -import java.util.Locale; - import java.util.ArrayList; +import java.util.Locale; import butterknife.BindView; import butterknife.ButterKnife; @@ -85,7 +85,9 @@ public class AlbumDetailActivity extends AbsSlidingMusicPanelActivity implements @BindView(R.id.toolbar) Toolbar toolbar; @BindView(R.id.title) - TextView albumTitleView; + RelativeLayout albumTitleView; + @BindView(R.id.album_title) + TextView albumTitleTextView; @BindView(R.id.list_background) View songsBackgroundView; @@ -196,8 +198,7 @@ public void onColorReady(int color) { private void setColors(int color) { toolbarColor = color; albumTitleView.setBackgroundColor(color); - albumTitleView.setTextColor(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(color))); - + albumTitleTextView.setTextColor(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(color))); setNavigationbarColor(color); setTaskDescriptionColor(color); } @@ -427,7 +428,8 @@ private void setAlbum(Album album) { loadWiki(); } - albumTitleView.setText(album.getTitle()); + albumTitleTextView.setText(album.getTitle()); + adapter.swapDataSet(album.songs); } diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java index a63fe2c7a..2f9edc2b7 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/ArtistDetailActivity.java @@ -18,6 +18,7 @@ import android.view.MenuItem; import android.view.View; import android.widget.ImageView; +import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; @@ -58,9 +59,8 @@ import com.kabouzeid.gramophone.util.PreferenceUtil; import com.kabouzeid.gramophone.util.Util; -import java.util.Locale; - import java.util.ArrayList; +import java.util.Locale; import butterknife.BindView; import butterknife.ButterKnife; @@ -85,7 +85,9 @@ public class ArtistDetailActivity extends AbsSlidingMusicPanelActivity implement @BindView(R.id.list) ObservableListView songListView; @BindView(R.id.title) - TextView artistName; + RelativeLayout artistName; + @BindView(R.id.album_title) + TextView artistNameTextView; @BindView(R.id.toolbar) Toolbar toolbar; @@ -323,7 +325,7 @@ public int getPaletteColor() { private void setColors(int color) { toolbarColor = color; artistName.setBackgroundColor(color); - artistName.setTextColor(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(color))); + artistNameTextView.setTextColor(MaterialValueHelper.getPrimaryTextColor(this, ColorUtil.isColorLight(color))); setNavigationbarColor(color); setTaskDescriptionColor(color); } @@ -457,7 +459,7 @@ private void setArtist(Artist artist) { loadBiography(); } - artistName.setText(artist.getName()); + artistNameTextView.setText(artist.getName()); songAdapter.swapDataSet(artist.getSongs()); albumAdapter.swapDataSet(artist.albums); } diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/PlaylistDetailActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/PlaylistDetailActivity.java index 641a63a1b..5cc35eda7 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/PlaylistDetailActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/PlaylistDetailActivity.java @@ -20,8 +20,8 @@ import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils; import com.kabouzeid.appthemehelper.ThemeStore; import com.kabouzeid.gramophone.R; -import com.kabouzeid.gramophone.adapter.song.PlaylistSongAdapter; import com.kabouzeid.gramophone.adapter.song.OrderablePlaylistSongAdapter; +import com.kabouzeid.gramophone.adapter.song.PlaylistSongAdapter; import com.kabouzeid.gramophone.adapter.song.SongAdapter; import com.kabouzeid.gramophone.dialogs.SleepTimerDialog; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/MusicUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/MusicUtil.java index 4f299565c..04687eb7b 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/util/MusicUtil.java +++ b/app/src/main/java/com/kabouzeid/gramophone/util/MusicUtil.java @@ -23,7 +23,6 @@ import com.kabouzeid.gramophone.loader.SongLoader; import com.kabouzeid.gramophone.model.Artist; import com.kabouzeid.gramophone.model.Playlist; -import com.kabouzeid.gramophone.model.PlaylistSong; import com.kabouzeid.gramophone.model.Song; import com.kabouzeid.gramophone.model.lyrics.AbsSynchronizedLyrics; diff --git a/app/src/main/java/com/kabouzeid/gramophone/views/AutoTruncateTextView.java b/app/src/main/java/com/kabouzeid/gramophone/views/AutoTruncateTextView.java new file mode 100644 index 000000000..86ff3124d --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/views/AutoTruncateTextView.java @@ -0,0 +1,218 @@ +package com.kabouzeid.gramophone.views; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.v7.widget.AppCompatTextView; +import android.text.TextUtils; +import android.util.AttributeSet; + +/** + * @author Lincoln (theduffmaster) + * + * TextView that automatically does exactly what android:ellipsize="end" does, except this works in + * a {@link TouchInterceptHorizontalScrollView}. + * Truncates the string so it doesn't get cuttoff in the {@link TouchInterceptHorizontalScrollView} + * and puts an ellipsis at the end of it. + * Must be used within a {@link TouchInterceptHorizontalScrollView}. + */ +public class AutoTruncateTextView extends AppCompatTextView { + + public static final String TAG = AutoTruncateTextView.class.getSimpleName(); + + private static final int RETRUNCATE_DELAY = 600; + + // Invisible character used as a marker indicating whether a string has undergone truncation + private static final String TRUNCATED_MARKER = "\u202F"; + + // Invisible character used as a marker indicating whether a string is untruncated + private static final String MARKER_UNTRUNCATED = "\uFEFF"; + + private String text; + private String truncatedText; + + public AutoTruncateTextView(Context context) { + super(context); + init(); + } + + public AutoTruncateTextView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public AutoTruncateTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + setTag(AutoTruncateTextView.TAG); + + // Enable long clicking when touching the text + setLongClickable(true); + + // Blocks clicks from passing through this view + setClickable(true); + + // Have to use this instead of maxlines in order for scrolling to work + setSingleLine(); + } + + /** + * @return Returns the {@link TouchInterceptFrameLayout} inside this layout. + */ + public TouchInterceptFrameLayout getTouchInterceptFrameLayout() { + return (TouchInterceptFrameLayout) getRootView().findViewWithTag(TouchInterceptFrameLayout.TAG); + } + + /** + * @return Returns the parent {@link TouchInterceptHorizontalScrollView}. + */ + public TouchInterceptHorizontalScrollView getTouchInterceptHorizontalScrollView() { + return (TouchInterceptHorizontalScrollView) getParent(); + } + + /** + * The text undergoes truncation here. This is immediately called after {@link #setText} and has + * a reference to the parent's bounds. The bounds are used for setting the length of the + * truncated text, ensuring that the text does not get visibly cut off. + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + String fittedText = getText().toString(); + + final int textBoundsWidth = MeasureSpec.getSize(widthMeasureSpec); + final boolean isUntruncated = fittedText.endsWith(MARKER_UNTRUNCATED); + + if (!fittedText.endsWith(TRUNCATED_MARKER) && !isUntruncated) { + this.text = fittedText; + } + + if (!isUntruncated && (getWidth() == 0 | textBoundsWidth < getPaint().measureText(fittedText))) { + // Mimics behavior of `android:ellipsize="end"`, except it works in a HorizontalScrollView. + // Truncates the string so it doesn't get cut off in the HorizontalScrollView with an + // ellipsis at the end of it. + final String ellipsizedText = TextUtils.ellipsize(fittedText, + getPaint(), + (float) textBoundsWidth, + TextUtils.TruncateAt.END).toString(); + fittedText = ellipsizedText + TRUNCATED_MARKER; + } + + setText(fittedText); + initiateTruncateText(text, fittedText); + } + + /** + * Takes the string that's undergone truncation and based on whether it's been truncated or not + * set whether it should be scrollable or not and what to do when the user finishes scrolling. + * + * @param originalText The string before truncation + * @param truncatedText The string after truncation + */ + public void initiateTruncateText(final String originalText, final String truncatedText) { + if (!originalText.endsWith(TRUNCATED_MARKER)) { + this.text = originalText; + } + this.truncatedText = truncatedText; + + final TouchInterceptHorizontalScrollView scrollView = getTouchInterceptHorizontalScrollView(); + post(new Runnable() { + @Override + public void run() { + if (isTruncated(truncatedText)) { + if (originalText.equals(truncatedText) && !truncatedText.endsWith(MARKER_UNTRUNCATED)) { + scrollView.setScrollable(false); + } else { + scrollView.setScrollable(true); + scrollView.setOnEndScrollListener(new TouchInterceptHorizontalScrollView.OnEndScrollListener() { + @Override + public void onEndScroll() { + retruncateScrollText(truncatedText); + } + }); + } + } else if (!truncatedText.endsWith(MARKER_UNTRUNCATED)) { + scrollView.setScrollable(false); + } + } + }); + } + + /** + * Checks whether a string was truncated at some point. + * + * @param text The string to check. + * @return Returns whether the text has been truncated or not. + */ + public boolean isTruncated(final String text) { + return text.endsWith("…" + TRUNCATED_MARKER); + } + + /** + * Checks whether a string was untruncated at some point. + * + * @return Returns whether the current text has been untruncated or not. + */ + public boolean isUntruncated() { + return getText().toString().endsWith(MARKER_UNTRUNCATED); + } + + /** + * Untruncates and sets the text. + */ + public void untruncateText() { + String untrunucatedText = text + MARKER_UNTRUNCATED; + setText(untrunucatedText); + } + + /** + * @return Returns the truncated text. + */ + public String getTruncatedText() { + return this.truncatedText; + } + + /** + * @return Returns the untruncated text. + */ + public String getUntruncatedText() { + return this.text; + } + + /** + * Re-truncates the text and animates it scrolling back to the start position. + */ + public void retruncateScrollText(final String truncatedText) { + Animator animator = ObjectAnimator + .ofInt(getTouchInterceptHorizontalScrollView(), "scrollX", 0) + .setDuration(RETRUNCATE_DELAY); + + animator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animator) { + } + + @Override + public void onAnimationEnd(Animator animator) { + if (isUntruncated()) { + setText(truncatedText); + } + } + + @Override + public void onAnimationCancel(Animator animator) { + } + + @Override + public void onAnimationRepeat(Animator animator) { + } + }); + + animator.start(); + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/views/TouchInterceptFrameLayout.java b/app/src/main/java/com/kabouzeid/gramophone/views/TouchInterceptFrameLayout.java new file mode 100644 index 000000000..ad16be528 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/views/TouchInterceptFrameLayout.java @@ -0,0 +1,149 @@ +package com.kabouzeid.gramophone.views; + +import android.content.Context; +import android.graphics.Rect; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.FrameLayout; + +/** + * @author Lincoln (theduffmaster) + * + * A custom {@link FrameLayout} that intercepts touch events and decides whether to consume them or + * pass them on to a child {@link TouchInterceptHorizontalScrollView} and its + * {@link AutoTruncateTextView}. + * + * This only needs to be used if the layout containing the {@link TouchInterceptHorizontalScrollView} + * is clickable. + */ +public class TouchInterceptFrameLayout extends FrameLayout { + + public static final String TAG = TouchInterceptFrameLayout.class.getSimpleName(); + + private static final int MAX_CLICK_DISTANCE = 5; + + private TouchInterceptHorizontalScrollView scrollView; + + private Rect scrollViewRect; + private float startX; + private boolean isTap; + + public TouchInterceptFrameLayout(@NonNull Context context) { + this(context, null); + init(); + } + + public TouchInterceptFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + init(); + } + + public TouchInterceptFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + scrollViewRect = new Rect(); + setTag(TouchInterceptFrameLayout.TAG); + } + + /** + * @return Returns the child {@link TouchInterceptHorizontalScrollView}. + */ + public TouchInterceptHorizontalScrollView getTouchInterceptHorizontalScrollView() { + return (TouchInterceptHorizontalScrollView) findViewWithTag(TouchInterceptHorizontalScrollView.TAG); + } + + /** + * Intercepts touch events to selectively pass the event on to its child view. + * It also detects where the touch was placed so that if the touch is not in the scrollview, the + * touch is not passed to it, avoiding the child view swallowing up the long press. ACTION_MOVE + * actions are cancelled here and instead passed to the child view. + * + * @param e The intercepted touch event. + * @return True if the MotionEvent will be intercepted (i.e. it will not be passed on to its + * child, but rather to the onTouchEvent method of this view). + */ + @Override + public boolean onInterceptTouchEvent(MotionEvent e) { + int x = Math.round(e.getRawX()); + int y = Math.round(e.getRawY()); + + scrollView = getTouchInterceptHorizontalScrollView(); + scrollView.getGlobalVisibleRect(scrollViewRect); + + boolean touchedScrollView = + x > scrollViewRect.left && x < scrollViewRect.right && + y > scrollViewRect.top && y < scrollViewRect.bottom; + + if (scrollView.isScrollable()) { + switch (e.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + if (!touchedScrollView) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + scrollView.cancelPendingInputEvents(); + } else { + scrollView.cancelLongPress(); + } + return false; + } + + startX = e.getX(); + isTap = true; + onTouchEvent(e); + break; + + case MotionEvent.ACTION_MOVE: + if (touchedScrollView) { + float distance = Math.abs(e.getX() - startX); + + // Scrolling the view: cancel event to prevent long press + if (distance > MAX_CLICK_DISTANCE) { + isTap = false; + cancelLongClick(); + } + } + break; + + case MotionEvent.ACTION_CANCEL: + // Long click cancels should have an x coordinate of 0 during ACTION_CANCEL. + // If it does not, then it is not a long click so cancel it. + if (e.getX() != 0) { + cancelLongClick(); + } + break; + + case MotionEvent.ACTION_UP: + if (touchedScrollView && isTap) { + onTouchEvent(e); + } + break; + } + + return false; + } + + if (touchedScrollView) { + // Long click cancels should have an x coordinate of 0 during ACTION_CANCEL. + // If it does not, then it is not a long click so cancel it. + if (e.getAction() == MotionEvent.ACTION_CANCEL && e.getX() != 0) { + cancelLongClick(); + } + onTouchEvent(e); + } + return false; + } + + /** + * Cancels any long presses. Used to prevent views from stealing touches while the user is + * scrolling something. + */ + private void cancelLongClick() { + scrollView.cancelLongPress(); + this.cancelLongPress(); + } +} diff --git a/app/src/main/java/com/kabouzeid/gramophone/views/TouchInterceptHorizontalScrollView.java b/app/src/main/java/com/kabouzeid/gramophone/views/TouchInterceptHorizontalScrollView.java new file mode 100644 index 000000000..12447a1c8 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/views/TouchInterceptHorizontalScrollView.java @@ -0,0 +1,249 @@ +package com.kabouzeid.gramophone.views; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.HorizontalScrollView; + +/** + * @author Lincoln (theduffmaster) + * + * A custom {@link HorizontalScrollView} that is only useful as the child of a + * {@link TouchInterceptFrameLayout}. Allows for the layout to disable and enable scrolling in + * addition to being able to know when a user is and is not interacting with the scrolling view. + * + * Must have a {@link AutoTruncateTextView} as its only child. + */ +public class TouchInterceptHorizontalScrollView extends HorizontalScrollView { + + public static final String TAG = TouchInterceptHorizontalScrollView.class.getSimpleName(); + + /** Delay before triggering {@link OnEndScrollListener#onEndScroll} */ + private static final int ON_END_SCROLL_DELAY = 1000; + + private static final int MAX_CLICK_DISTANCE = 5; + + private float startX; + private long lastScrollUpdate = -1; + private boolean scrollable; + private boolean isFling; + private Rect scrollViewRect; + private OnEndScrollListener onEndScrollListener; + + // Whether user is interacting with this again and to cancel text retruncate + private boolean cancel; + private boolean cancelCheck; + + // ID of the active pointer + private int activePointerId; + + // Whether to untruncate the text in the TouchInterceptTextView + private boolean untruncate; + + public TouchInterceptHorizontalScrollView(Context context) { + super(context); + init(); + } + + public TouchInterceptHorizontalScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public TouchInterceptHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void init() { + lastScrollUpdate = -1; + scrollable = true; + scrollViewRect = new Rect(); + setLongClickable(false); + setTag(TouchInterceptHorizontalScrollView.TAG); + setHorizontalScrollBarEnabled(false); + } + + public TouchInterceptFrameLayout getTouchInterceptFrameLayout() { + return (TouchInterceptFrameLayout) getRootView().findViewWithTag(TouchInterceptFrameLayout.TAG); + } + + /** + * @return Returns the child {@link AutoTruncateTextView}. + */ + public AutoTruncateTextView getTouchInterceptTextView() { + return (AutoTruncateTextView) this.getChildAt(0); + } + + /** + * @return Returns the set {@link OnEndScrollListener}. + */ + public OnEndScrollListener getOnEndScrollListener() { + return onEndScrollListener; + } + + /** + * Disables and enables scrolling. + * + * @param scrollable Whether the view should be scrollable. + */ + public void setScrollable(boolean scrollable) { + this.scrollable = scrollable; + } + + /** + * Returns whether the view is scrollable. + * + * @return Whether the view is scrollable. + */ + public boolean isScrollable() { + return scrollable; + } + + @Override + public boolean onTouchEvent(MotionEvent e) { + switch (e.getAction()) { + case MotionEvent.ACTION_DOWN: + cancel = true; + startX = e.getX(); + + // If we can scroll, pass the event to the superclass + if (scrollable) { + return super.onTouchEvent(e); + } + + // Don't continue to handle the touch event if scrolling is disabled + return false; + + case MotionEvent.ACTION_MOVE: + float distance = Math.abs(e.getX() - startX); + + // Currently scrolling, so untruncate text + if (untruncate && distance > MAX_CLICK_DISTANCE) { + getTouchInterceptTextView().untruncateText(); + untruncate = false; + } + + case MotionEvent.ACTION_UP: + // User is done interacting with the scroll view + cancel = false; + postDelayed(new ScrollStateHandler(), ON_END_SCROLL_DELAY); + lastScrollUpdate = System.currentTimeMillis(); + untruncate = true; + + default: + return super.onTouchEvent(e); + } + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent e) { + int x = Math.round(e.getRawX()); + int y = Math.round(e.getRawY()); + + // Check to see if it's a valid pointerID. + // If it's invalid, a long click is triggered. This stops that. + switch (e.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + activePointerId = e.getPointerId(0); + break; + + case MotionEvent.ACTION_MOVE: + if (e.findPointerIndex(activePointerId) == -1) { + cancelLongClick(); + } + break; + } + + getGlobalVisibleRect(scrollViewRect); + + boolean touchedScrollView = + x > scrollViewRect.left && x < scrollViewRect.right && + y > scrollViewRect.top && y < scrollViewRect.bottom; + + if (!touchedScrollView) { + return false; + } + + // Don't do anything with intercepted touch events if not scrollable + if (!scrollable) { + onTouchEvent(e); + return false; + } + + return super.onInterceptTouchEvent(e); + } + + /** + * Sets the {@link OnEndScrollListener}. + * + * @param onEndScrollListener The listener to be set. + */ + public void setOnEndScrollListener(OnEndScrollListener onEndScrollListener) { + this.onEndScrollListener = onEndScrollListener; + } + + @Override + public void fling(int velocityX) { + super.fling(velocityX); + isFling = true; + } + + @Override + protected void onScrollChanged(int x, int y, int oldX, int oldY) { + super.onScrollChanged(x, y, oldX, oldY); + + cancelLongClick(); + + if (cancelCheck) { + cancel = true; + } + + if (isFling && (Math.abs(x - oldX) < 2 || x >= getMeasuredWidth() || x == 0)) { + // User is done interacting with the scroll view + cancel = false; + postDelayed(new ScrollStateHandler(), ON_END_SCROLL_DELAY); + lastScrollUpdate = System.currentTimeMillis(); + isFling = false; + cancelCheck = false; + untruncate = true; + } + } + + /** + * Cancels any long presses. Used to prevent views from stealing touches while the user is + * scrolling something. + */ + private void cancelLongClick() { + getRootView().cancelLongPress(); + this.cancelLongPress(); + } + + interface OnEndScrollListener { + /** + * Triggered when a user has stopped interacting with the + * {@link TouchInterceptHorizontalScrollView}. + */ + void onEndScroll(); + } + + private class ScrollStateHandler implements Runnable { + @Override + public void run() { + if (!cancel) { + // Hasn't been touched for some time + long currentTime = System.currentTimeMillis(); + if ((currentTime - lastScrollUpdate) > ON_END_SCROLL_DELAY) { + lastScrollUpdate = -1; + if (onEndScrollListener != null) { + cancelCheck = true; + onEndScrollListener.onEndScroll(); + } + } else { + postDelayed(this, ON_END_SCROLL_DELAY); + } + } + } + } +} diff --git a/app/src/main/res/layout/activity_album_detail.xml b/app/src/main/res/layout/activity_album_detail.xml index 3b313d792..6f0bf9984 100644 --- a/app/src/main/res/layout/activity_album_detail.xml +++ b/app/src/main/res/layout/activity_album_detail.xml @@ -1,4 +1,5 @@ + - + android:paddingRight="72dp"> + + + + + + + + + - + android:paddingRight="72dp"> + + + + + + + + - + - + android:layout_height="wrap_content"> + + + + + - \ No newline at end of file + diff --git a/app/src/main/res/layout/item_list_single_row.xml b/app/src/main/res/layout/item_list_single_row.xml index 1a6ed7cb1..b316da291 100644 --- a/app/src/main/res/layout/item_list_single_row.xml +++ b/app/src/main/res/layout/item_list_single_row.xml @@ -1,5 +1,5 @@ - - + android:layout_weight="1"> + + + + + + + + - \ No newline at end of file +