From 3082ed1187ba8179548991720bab75af07837189 Mon Sep 17 00:00:00 2001 From: Karim Abou Zeid Date: Tue, 2 Jun 2015 21:27:02 +0200 Subject: [PATCH] Merged fastscroller from cabinet. Temporary fixed the wrong fab margins on KitKat and below caused by the google support design library. Added empty screens to Album Song and Artist views. --- app/build.gradle | 8 +- .../gramophone/adapter/AlbumAdapter.java | 11 +- .../gramophone/adapter/ArtistAdapter.java | 11 +- .../gramophone/adapter/PlaylistAdapter.java | 11 +- .../adapter/songadapter/SongAdapter.java | 14 +- .../interfaces/OnUpdatedListener.java | 9 + .../gramophone/interfaces/SelfUpdating.java | 9 + .../AbsMainActivityFragment.java | 9 - .../AbsMainActivityRecyclerViewFragment.java | 50 +++- .../AlbumViewFragment.java | 10 +- .../ArtistViewFragment.java | 15 +- .../PlaylistViewFragment.java | 52 +--- .../SongViewFragment.java | 10 +- .../com/kabouzeid/gramophone/util/Util.java | 8 + .../gramophone/views/FastScroller.java | 248 ++++++++++++++++++ .../main/res/layout/activity_album_detail.xml | 12 +- .../res/layout/activity_album_tag_editor.xml | 18 +- .../res/layout/activity_artist_detail.xml | 12 +- app/src/main/res/layout/activity_main.xml | 8 +- .../res/layout/activity_music_controller.xml | 13 +- .../res/layout/activity_playlist_detail.xml | 6 +- .../res/layout/activity_song_tag_editor.xml | 26 +- .../main/res/layout/fragment_album_view.xml | 19 -- .../main/res/layout/fragment_artist_view.xml | 15 -- ... fragment_main_activity_recycler_view.xml} | 20 +- app/src/main/res/layout/fragment_songview.xml | 18 -- ...vertical_recycler_fast_scroller_layout.xml | 21 ++ app/src/main/res/values-v21/dimens.xml | 3 + app/src/main/res/values/dimens.xml | 8 + app/src/main/res/values/strings.xml | 4 + 30 files changed, 483 insertions(+), 195 deletions(-) create mode 100644 app/src/main/java/com/kabouzeid/gramophone/interfaces/OnUpdatedListener.java create mode 100644 app/src/main/java/com/kabouzeid/gramophone/interfaces/SelfUpdating.java create mode 100644 app/src/main/java/com/kabouzeid/gramophone/views/FastScroller.java delete mode 100644 app/src/main/res/layout/fragment_album_view.xml delete mode 100644 app/src/main/res/layout/fragment_artist_view.xml rename app/src/main/res/layout/{fragment_playlist_view.xml => fragment_main_activity_recycler_view.xml} (56%) delete mode 100644 app/src/main/res/layout/fragment_songview.xml create mode 100644 app/src/main/res/layout/vertical_recycler_fast_scroller_layout.xml diff --git a/app/build.gradle b/app/build.gradle index c7cf4bf8..599747a5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -27,14 +27,10 @@ android { applicationId "com.kabouzeid.gramophone" minSdkVersion 16 targetSdkVersion 22 - versionCode 29 - versionName "0.9.13b DEV-3" + versionCode 30 + versionName "0.9.14b DEV" } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_7 - targetCompatibility JavaVersion.VERSION_1_7 - } buildTypes { release { minifyEnabled true diff --git a/app/src/main/java/com/kabouzeid/gramophone/adapter/AlbumAdapter.java b/app/src/main/java/com/kabouzeid/gramophone/adapter/AlbumAdapter.java index cd9f06b0..92b08e6d 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/adapter/AlbumAdapter.java +++ b/app/src/main/java/com/kabouzeid/gramophone/adapter/AlbumAdapter.java @@ -23,6 +23,8 @@ import com.kabouzeid.gramophone.dialogs.AddToPlaylistDialog; import com.kabouzeid.gramophone.dialogs.DeleteSongsDialog; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.interfaces.CabHolder; +import com.kabouzeid.gramophone.interfaces.OnUpdatedListener; +import com.kabouzeid.gramophone.interfaces.SelfUpdating; import com.kabouzeid.gramophone.loader.AlbumLoader; import com.kabouzeid.gramophone.loader.AlbumSongLoader; import com.kabouzeid.gramophone.model.Album; @@ -47,12 +49,13 @@ import java.util.List; /** * @author Karim Abou Zeid (kabouzeid) */ -public class AlbumAdapter extends AbsMultiSelectAdapter { +public class AlbumAdapter extends AbsMultiSelectAdapter implements SelfUpdating { public static final String TAG = AlbumAdapter.class.getSimpleName(); private final AppCompatActivity activity; private boolean usePalette; private List dataSet; + private OnUpdatedListener listener; @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { @@ -130,6 +133,11 @@ public class AlbumAdapter extends AbsMultiSelectAdapter { +public class ArtistAdapter extends AbsMultiSelectAdapter implements SelfUpdating { protected final AppCompatActivity activity; protected List dataSet; + private OnUpdatedListener listener; public ArtistAdapter(AppCompatActivity activity, @Nullable CabHolder cabHolder) { super(cabHolder, R.menu.menu_media_selection); @@ -48,6 +51,7 @@ public class ArtistAdapter extends AbsMultiSelectAdapter { +public class PlaylistAdapter extends AbsMultiSelectAdapter implements SelfUpdating { public static final String TAG = PlaylistAdapter.class.getSimpleName(); protected final AppCompatActivity activity; protected List dataSet; + private OnUpdatedListener listener; public PlaylistAdapter(AppCompatActivity activity, @Nullable CabHolder cabHolder) { super(cabHolder, R.menu.menu_playlists_selection); @@ -47,6 +50,7 @@ public class PlaylistAdapter extends AbsMultiSelectAdapter implements MaterialCab.Callback { +public class SongAdapter extends AbsMultiSelectAdapter implements MaterialCab.Callback, SelfUpdating { public static final String TAG = AlbumSongAdapter.class.getSimpleName(); private static final int SHUFFLE_BUTTON = 0; @@ -45,6 +47,7 @@ public class SongAdapter extends AbsMultiSelectAdapter dataSet; + private OnUpdatedListener listener; public SongAdapter(AppCompatActivity activity, CabHolder cabHolder) { super(cabHolder, R.menu.menu_media_selection); @@ -54,6 +57,7 @@ public class SongAdapter extends AbsMultiSelectAdapter 0 ? dataSetSize + 1 : 0; } @Override @@ -124,6 +129,11 @@ public class SongAdapter extends AbsMultiSelectAdapter= Build.VERSION_CODES.JELLY_BEAN_MR1) { + Configuration config = context.getResources().getConfiguration(); + return config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + } else return false; + } } \ No newline at end of file diff --git a/app/src/main/java/com/kabouzeid/gramophone/views/FastScroller.java b/app/src/main/java/com/kabouzeid/gramophone/views/FastScroller.java new file mode 100644 index 00000000..71ce53b2 --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/views/FastScroller.java @@ -0,0 +1,248 @@ +package com.kabouzeid.gramophone.views; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; +import android.graphics.drawable.StateListDrawable; +import android.support.v4.view.animation.FastOutLinearInInterpolator; +import android.support.v4.view.animation.LinearOutSlowInInterpolator; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; + +import com.kabouzeid.gramophone.R; +import com.kabouzeid.gramophone.util.Util; + +/** + * Defines a basic widget that will allow for fast scrolling a RecyclerView using the basic paradigm of + * a handle and a bar. + */ +public class FastScroller extends FrameLayout { + + /** + * The long bar along which a handle travels + */ + protected final View mBar; + /** + * The handle that signifies the user's progress in the list + */ + protected final View mHandle; + protected RecyclerView.OnScrollListener mOnScrollListener; + protected OnTouchListener mOnTouchListener; + private RecyclerView mRecyclerView; + private AnimatorSet mAnimator; + private boolean animatingIn; + private final Runnable mHide; + private final int mMinScrollHandleHeight; + private final int mHiddenTranslationX; + + public FastScroller(Context context) { + this(context, null, 0); + } + + public FastScroller(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public FastScroller(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + inflate(context, R.layout.vertical_recycler_fast_scroller_layout, this); + + mBar = findViewById(R.id.scroll_bar); + mHandle = findViewById(R.id.scroll_handle); + mMinScrollHandleHeight = getResources().getDimensionPixelSize(R.dimen.min_scrollhandle_height); + + mHiddenTranslationX = (Util.isRTL(getContext()) ? -1 : 1) * getResources().getDimensionPixelSize(R.dimen.scrollbar_width); + mHide = new Runnable() { + @Override + public void run() { + if (!mHandle.isPressed()) { + if (mAnimator != null && mAnimator.isStarted()) { + mAnimator.cancel(); + } + mAnimator = new AnimatorSet(); + ObjectAnimator animator2 = ObjectAnimator.ofFloat(FastScroller.this, View.TRANSLATION_X, + mHiddenTranslationX); + animator2.setInterpolator(new FastOutLinearInInterpolator()); + animator2.setDuration(150); + mHandle.setEnabled(false); + mAnimator.play(animator2); + mAnimator.start(); + } + } + }; + + mHandle.setOnTouchListener(new OnTouchListener() { + private float mInitialBarHeight; + private float mLastPressedYAdjustedToInitial; + + @Override + public boolean onTouch(View v, MotionEvent event) { + mOnTouchListener.onTouch(v, event); + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mHandle.setPressed(true); + + mInitialBarHeight = mBar.getHeight(); + mLastPressedYAdjustedToInitial = event.getY() + mHandle.getY() + mBar.getY(); + } else if (event.getActionMasked() == MotionEvent.ACTION_MOVE) { + float newHandlePressedY = event.getY() + mHandle.getY() + mBar.getY(); + int barHeight = mBar.getHeight(); + float newHandlePressedYAdjustedToInitial = + newHandlePressedY + (mInitialBarHeight - barHeight); + + float deltaPressedYFromLastAdjustedToInitial = + newHandlePressedYAdjustedToInitial - mLastPressedYAdjustedToInitial; + + int dY = (int) ((deltaPressedYFromLastAdjustedToInitial / mInitialBarHeight) * + (mRecyclerView.computeVerticalScrollRange())); + + updateRvScroll(dY); + + mLastPressedYAdjustedToInitial = newHandlePressedYAdjustedToInitial; + } else if (event.getActionMasked() == MotionEvent.ACTION_UP) { + mLastPressedYAdjustedToInitial = -1; + + mHandle.setPressed(false); + postAutoHide(); + } + + return true; + } + }); + + setTranslationX(mHiddenTranslationX); + + //Default selected handle color + setPressedHandleColor(Color.BLACK); + setUpBarBackground(); + } + + /** + * Provides the ability to programmatically set the color of the fast scroller's handle + */ + public void setPressedHandleColor(int accent) { + StateListDrawable drawable = new StateListDrawable(); + + int colorControlNormal = Util.resolveColor(getContext(), R.attr.colorControlNormal); + + if (!Util.isRTL(getContext())) { + drawable.addState(View.PRESSED_ENABLED_STATE_SET, + new InsetDrawable(new ColorDrawable(accent), getResources().getDimensionPixelSize(R.dimen.scrollbar_inset), 0, 0, 0)); + drawable.addState(View.EMPTY_STATE_SET, + new InsetDrawable(new ColorDrawable(colorControlNormal), getResources().getDimensionPixelSize(R.dimen.scrollbar_inset), 0, 0, 0)); + } else { + drawable.addState(View.PRESSED_ENABLED_STATE_SET, + new InsetDrawable(new ColorDrawable(accent), 0, getResources().getDimensionPixelSize(R.dimen.scrollbar_inset), 0, 0)); + drawable.addState(View.EMPTY_STATE_SET, + new InsetDrawable(new ColorDrawable(colorControlNormal), 0, getResources().getDimensionPixelSize(R.dimen.scrollbar_inset), 0, 0)); + } + mHandle.setBackground(drawable); + } + + private void setUpBarBackground() { + Drawable drawable; + + int colorControlNormal = Util.resolveColor(getContext(), R.attr.colorControlNormal); + + if (!Util.isRTL(getContext())) { + drawable = new InsetDrawable(new ColorDrawable(colorControlNormal), getResources().getDimensionPixelSize(R.dimen.scrollbar_inset), 0, 0, 0); + } else { + drawable = new InsetDrawable(new ColorDrawable(colorControlNormal), 0, getResources().getDimensionPixelSize(R.dimen.scrollbar_inset), 0, 0); + } + mBar.setBackground(drawable); + } + + public void setRecyclerView(RecyclerView recyclerView) { + mRecyclerView = recyclerView; + initRecyclerViewOnScrollListener(); + } + + public void initRecyclerViewOnScrollListener() { + mOnScrollListener = new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(RecyclerView recyclerView, int dx, int dy) { + super.onScrolled(recyclerView, dx, dy); + requestLayout(); + + mHandle.setEnabled(true); + if (!animatingIn && getTranslationX() != 0) { + if (mAnimator != null && mAnimator.isStarted()) { + mAnimator.cancel(); + } + mAnimator = new AnimatorSet(); + ObjectAnimator animator = ObjectAnimator.ofFloat(FastScroller.this, View.TRANSLATION_X, 0); + animator.setInterpolator(new LinearOutSlowInInterpolator()); + animator.setDuration(100); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + animatingIn = false; + } + }); + animatingIn = true; + mAnimator.play(animator); + mAnimator.start(); + } + postAutoHide(); + } + }; + mRecyclerView.addOnScrollListener(mOnScrollListener); + } + + public void setOnHandleTouchListener(OnTouchListener listener) { + mOnTouchListener = listener; + } + + private void postAutoHide() { + if (mRecyclerView != null) { + mRecyclerView.removeCallbacks(mHide); + mRecyclerView.postDelayed(mHide, 1500); + } + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + int scrollOffset = mRecyclerView.computeVerticalScrollOffset(); + int verticalScrollRange = mRecyclerView.computeVerticalScrollRange() + + mRecyclerView.getPaddingBottom(); + + int barHeight = mBar.getHeight(); + float ratio = (float) scrollOffset / (verticalScrollRange - barHeight); + + int calculatedHandleHeight = (int) ((float) barHeight / verticalScrollRange * barHeight); + if (calculatedHandleHeight < mMinScrollHandleHeight) { + calculatedHandleHeight = mMinScrollHandleHeight; + } + + if (calculatedHandleHeight >= barHeight) { + setTranslationX(mHiddenTranslationX); + return; + } + + float y = ratio * (barHeight - calculatedHandleHeight); + + mHandle.layout(mHandle.getLeft(), (int) y, mHandle.getRight(), (int) y + calculatedHandleHeight); + } + + public void updateRvScroll(int dY) { + if (mRecyclerView != null && mHandle != null) { + try { + mRecyclerView.scrollBy(0, dY); + } catch (Throwable t) { + t.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_album_detail.xml b/app/src/main/res/layout/activity_album_detail.xml index 5aabbe5d..c2d8b0f3 100644 --- a/app/src/main/res/layout/activity_album_detail.xml +++ b/app/src/main/res/layout/activity_album_detail.xml @@ -1,12 +1,12 @@ + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + android:layout_height="@dimen/header_image_height" + android:background="?android:colorBackground"> + android:layout_margin="@dimen/fab_margin" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_album_tag_editor.xml b/app/src/main/res/layout/activity_album_tag_editor.xml index 9645cda4..f77e7a1e 100644 --- a/app/src/main/res/layout/activity_album_tag_editor.xml +++ b/app/src/main/res/layout/activity_album_tag_editor.xml @@ -1,10 +1,10 @@ + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fitsSystemWindows="true" + android:focusable="true" + android:focusableInTouchMode="true"> + tools:ignore="UnusedAttribute"> diff --git a/app/src/main/res/layout/activity_artist_detail.xml b/app/src/main/res/layout/activity_artist_detail.xml index 6012a79c..c4bdfcb3 100644 --- a/app/src/main/res/layout/activity_artist_detail.xml +++ b/app/src/main/res/layout/activity_artist_detail.xml @@ -1,13 +1,13 @@ + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + android:layout_height="@dimen/header_image_height" + android:background="?android:colorBackground"> + android:layout_margin="@dimen/fab_margin" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 07de4165..71719adb 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,8 +1,8 @@ + android:layout_height="wrap_content" + app:layout_scrollFlags="scroll|enterAlways"> + android:layout_margin="@dimen/fab_margin" /> diff --git a/app/src/main/res/layout/activity_music_controller.xml b/app/src/main/res/layout/activity_music_controller.xml index 90a87729..89bc6481 100644 --- a/app/src/main/res/layout/activity_music_controller.xml +++ b/app/src/main/res/layout/activity_music_controller.xml @@ -1,18 +1,18 @@ + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="com.kabouzeid.gramophone.ui.activities.MusicControllerActivity"> + android:layout_centerInParent="true" + android:layout_margin="@dimen/tmp_no_fab_margin" /> diff --git a/app/src/main/res/layout/activity_playlist_detail.xml b/app/src/main/res/layout/activity_playlist_detail.xml index 390f1872..e5bd9aed 100644 --- a/app/src/main/res/layout/activity_playlist_detail.xml +++ b/app/src/main/res/layout/activity_playlist_detail.xml @@ -1,6 +1,6 @@ + android:layout_width="match_parent" + android:layout_height="match_parent"> + android:layout_margin="@dimen/fab_margin" /> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_song_tag_editor.xml b/app/src/main/res/layout/activity_song_tag_editor.xml index 82c1d9c2..04e3ea80 100644 --- a/app/src/main/res/layout/activity_song_tag_editor.xml +++ b/app/src/main/res/layout/activity_song_tag_editor.xml @@ -1,10 +1,10 @@ + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fitsSystemWindows="true" + android:focusable="true" + android:focusableInTouchMode="true"> + android:layout_height="wrap_content"> + android:layout_height="wrap_content"> + android:layout_height="wrap_content"> + android:layout_height="wrap_content"> diff --git a/app/src/main/res/layout/fragment_album_view.xml b/app/src/main/res/layout/fragment_album_view.xml deleted file mode 100644 index 47b18e91..00000000 --- a/app/src/main/res/layout/fragment_album_view.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_artist_view.xml b/app/src/main/res/layout/fragment_artist_view.xml deleted file mode 100644 index aafbad23..00000000 --- a/app/src/main/res/layout/fragment_artist_view.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/fragment_playlist_view.xml b/app/src/main/res/layout/fragment_main_activity_recycler_view.xml similarity index 56% rename from app/src/main/res/layout/fragment_playlist_view.xml rename to app/src/main/res/layout/fragment_main_activity_recycler_view.xml index cbef8e05..3a90e143 100644 --- a/app/src/main/res/layout/fragment_playlist_view.xml +++ b/app/src/main/res/layout/fragment_main_activity_recycler_view.xml @@ -1,18 +1,16 @@ - + android:scrollbars="none" /> - + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_songview.xml b/app/src/main/res/layout/fragment_songview.xml deleted file mode 100644 index ab75f8ff..00000000 --- a/app/src/main/res/layout/fragment_songview.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/vertical_recycler_fast_scroller_layout.xml b/app/src/main/res/layout/vertical_recycler_fast_scroller_layout.xml new file mode 100644 index 00000000..147373d0 --- /dev/null +++ b/app/src/main/res/layout/vertical_recycler_fast_scroller_layout.xml @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-v21/dimens.xml b/app/src/main/res/values-v21/dimens.xml index d071f4aa..44ba893d 100644 --- a/app/src/main/res/values-v21/dimens.xml +++ b/app/src/main/res/values-v21/dimens.xml @@ -2,4 +2,7 @@ 25dp 165dp + + 16dp + 0dp \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 1f76d430..4b2ca350 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -50,4 +50,12 @@ http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout 128dp 86dp + + 48dp + 8dp + 40dp + + + 0dp + -16dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 65266f3c..387ff074 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -140,6 +140,10 @@ Update Image This playlist is empty No playlists + No albums + No songs + No artists + Nothing here Playlist name Song Only available on Lollipop.