diff options
Diffstat (limited to 'app/src')
9 files changed, 339 insertions, 8 deletions
diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java index 7461af69..9116e587 100644 --- a/app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java +++ b/app/src/main/java/github/daneren2005/dsub/adapter/ArtistAdapter.java @@ -30,9 +30,10 @@ import github.daneren2005.dsub.domain.Artist; import github.daneren2005.dsub.domain.MusicFolder; import github.daneren2005.dsub.util.Util; import github.daneren2005.dsub.view.ArtistView; +import github.daneren2005.dsub.view.FastScroller; import github.daneren2005.dsub.view.UpdateView; -public class ArtistAdapter extends SectionAdapter<Artist> { +public class ArtistAdapter extends SectionAdapter<Artist> implements FastScroller.BubbleTextGetter { public static int VIEW_TYPE_ARTIST = 4; private List<MusicFolder> musicFolders; @@ -122,6 +123,17 @@ public class ArtistAdapter extends SectionAdapter<Artist> { return VIEW_TYPE_ARTIST; } + @Override + public String getTextToShowInBubble(int position) { + Artist artist = getItemForPosition(position); + + if(artist == null) { + return ""; + } else { + return artist.getName().substring(0, 1); + } + } + public interface OnMusicFolderChanged { void onMusicFolderChanged(MusicFolder musicFolder); } diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/PlaylistAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/PlaylistAdapter.java index 4221677e..d7a9e8d7 100644 --- a/app/src/main/java/github/daneren2005/dsub/adapter/PlaylistAdapter.java +++ b/app/src/main/java/github/daneren2005/dsub/adapter/PlaylistAdapter.java @@ -21,10 +21,11 @@ import java.util.List; import android.view.ViewGroup; import github.daneren2005.dsub.domain.Playlist; import github.daneren2005.dsub.util.ImageLoader; +import github.daneren2005.dsub.view.FastScroller; import github.daneren2005.dsub.view.PlaylistView; import github.daneren2005.dsub.view.UpdateView; -public class PlaylistAdapter extends SectionAdapter<Playlist> { +public class PlaylistAdapter extends SectionAdapter<Playlist> implements FastScroller.BubbleTextGetter { public static int VIEW_TYPE_PLAYLIST = 1; private ImageLoader imageLoader; @@ -58,4 +59,15 @@ public class PlaylistAdapter extends SectionAdapter<Playlist> { public int getItemViewType(Playlist playlist) { return VIEW_TYPE_PLAYLIST; } + + @Override + public String getTextToShowInBubble(int position) { + Playlist playlist = getItemForPosition(position); + + if(playlist == null) { + return ""; + } else { + return playlist.getName().substring(0, 1); + } + } } diff --git a/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java b/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java index dc94178d..c1b132c7 100644 --- a/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java +++ b/app/src/main/java/github/daneren2005/dsub/adapter/PodcastChannelAdapter.java @@ -17,12 +17,13 @@ package github.daneren2005.dsub.adapter; import android.content.Context; import android.view.ViewGroup; import github.daneren2005.dsub.domain.PodcastChannel; +import github.daneren2005.dsub.view.FastScroller; import github.daneren2005.dsub.view.PodcastChannelView; import github.daneren2005.dsub.view.UpdateView; import java.util.List; -public class PodcastChannelAdapter extends SectionAdapter<PodcastChannel>{ +public class PodcastChannelAdapter extends SectionAdapter<PodcastChannel> implements FastScroller.BubbleTextGetter { public static int VIEW_TYPE_PODCAST = 1; public PodcastChannelAdapter(Context context, List<PodcastChannel> podcasts, OnItemClickedListener listener) { @@ -44,4 +45,15 @@ public class PodcastChannelAdapter extends SectionAdapter<PodcastChannel>{ public int getItemViewType(PodcastChannel item) { return VIEW_TYPE_PODCAST; } + + @Override + public String getTextToShowInBubble(int position) { + PodcastChannel podcast = getItemForPosition(position); + + if(podcast == null) { + return ""; + } else { + return podcast.getName().substring(0, 1); + } + } } diff --git a/app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java b/app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java index 9dc3e363..adf22484 100644 --- a/app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java +++ b/app/src/main/java/github/daneren2005/dsub/fragments/SelectRecyclerFragment.java @@ -15,7 +15,6 @@ package github.daneren2005.dsub.fragments; -import android.content.Context; import android.os.Bundle; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.RecyclerView; @@ -38,10 +37,12 @@ import github.daneren2005.dsub.service.MusicServiceFactory; import github.daneren2005.dsub.util.Constants; import github.daneren2005.dsub.util.ProgressListener; import github.daneren2005.dsub.util.TabBackgroundTask; +import github.daneren2005.dsub.view.FastScroller; public abstract class SelectRecyclerFragment<T> extends SubsonicFragment implements SectionAdapter.OnItemClickedListener<T> { private static final String TAG = SelectRecyclerFragment.class.getSimpleName(); protected RecyclerView recyclerView; + protected FastScroller fastScroller; protected SectionAdapter<T> adapter; protected UpdateTask currentTask; protected List<T> objects; @@ -77,6 +78,7 @@ public abstract class SelectRecyclerFragment<T> extends SubsonicFragment impleme refreshLayout.setOnRefreshListener(this); recyclerView = (RecyclerView) rootView.findViewById(R.id.fragment_recycler); + fastScroller = (FastScroller) rootView.findViewById(R.id.fragment_fast_scroller); setupLayoutManager(); if(pullToRefresh) { @@ -184,6 +186,13 @@ public abstract class SelectRecyclerFragment<T> extends SubsonicFragment impleme public void done(List<T> result) { if (result != null && !result.isEmpty()) { recyclerView.setAdapter(adapter = getAdapter(result)); + if(adapter instanceof FastScroller.BubbleTextGetter) { + if(!fastScroller.isAttached()) { + fastScroller.attachRecyclerView(recyclerView); + } + } else if(fastScroller.isAttached()) { + fastScroller.detachRecyclerView(); + } onFinishRefresh(); recyclerView.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/github/daneren2005/dsub/view/FastScroller.java b/app/src/main/java/github/daneren2005/dsub/view/FastScroller.java new file mode 100644 index 00000000..ee7aef4b --- /dev/null +++ b/app/src/main/java/github/daneren2005/dsub/view/FastScroller.java @@ -0,0 +1,208 @@ +/* + This file is part of Subsonic. + Subsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + Subsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with Subsonic. If not, see <http://www.gnu.org/licenses/>. + Copyright 2015 (C) Scott Jackson +*/ + +package github.daneren2005.dsub.view; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import github.daneren2005.dsub.R; + +import static android.support.v7.widget.RecyclerView.OnScrollListener; + +public class FastScroller extends LinearLayout { + private static final int BUBBLE_ANIMATION_DURATION = 100; + private static final int TRACK_SNAP_RANGE = 5; + + private TextView bubble; + private View handle; + private RecyclerView recyclerView; + private final ScrollListener scrollListener = new ScrollListener(); + private int height; + + private ObjectAnimator currentAnimator = null; + + public FastScroller(final Context context,final AttributeSet attrs,final int defStyleAttr) { + super(context,attrs,defStyleAttr); + initialise(context); + } + + public FastScroller(final Context context) { + super(context); + initialise(context); + } + + public FastScroller(final Context context,final AttributeSet attrs) { + super(context,attrs); + initialise(context); + } + + private void initialise(Context context) { + setOrientation(HORIZONTAL); + setClipChildren(false); + LayoutInflater inflater = LayoutInflater.from(context); + inflater.inflate(R.layout.fast_scroller,this,true); + bubble = (TextView)findViewById(R.id.fastscroller_bubble); + handle = findViewById(R.id.fastscroller_handle); + bubble.setVisibility(INVISIBLE); + setVisibility(GONE); + } + + @Override + protected void onSizeChanged(int w,int h,int oldw,int oldh) { + super.onSizeChanged(w,h,oldw,oldh); + height = h; + } + + @Override + public boolean onTouchEvent(@NonNull MotionEvent event) { + final int action = event.getAction(); + switch(action) + { + case MotionEvent.ACTION_DOWN: + if(event.getX()<handle.getX()) + return false; + if(currentAnimator != null) + currentAnimator.cancel(); + if(bubble.getVisibility() == INVISIBLE) + showBubble(); + handle.setSelected(true); + case MotionEvent.ACTION_MOVE: + final float y = event.getY(); + setBubbleAndHandlePosition(y); + setRecyclerViewPosition(y); + return true; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + handle.setSelected(false); + hideBubble(); + return true; + } + return super.onTouchEvent(event); + } + + public void attachRecyclerView(RecyclerView recyclerView) { + this.recyclerView = recyclerView; + recyclerView.addOnScrollListener(scrollListener); + recyclerView.setVerticalScrollBarEnabled(false); + setVisibility(View.VISIBLE); + } + public void detachRecyclerView() { + recyclerView.removeOnScrollListener(scrollListener); + recyclerView.setVerticalScrollBarEnabled(true); + recyclerView = null; + setVisibility(View.GONE); + } + public boolean isAttached() { + return recyclerView != null; + } + + private void setRecyclerViewPosition(float y) { + if(recyclerView != null) + { + int itemCount = recyclerView.getAdapter().getItemCount(); + float proportion; + if(handle.getY() == 0) + proportion = 0f; + else if(handle.getY()+handle.getHeight()>=height-TRACK_SNAP_RANGE) + proportion = 1f; + else + proportion = y/(float)height; + int targetPos = getValueInRange(0,itemCount-1,(int)(proportion*(float)itemCount)); + ((LinearLayoutManager)recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos,0); + String bubbleText = ((BubbleTextGetter)recyclerView.getAdapter()).getTextToShowInBubble(targetPos); + bubble.setText(bubbleText); + } + } + + private int getValueInRange(int min,int max,int value) { + int minimum = Math.max(min,value); + return Math.min(minimum,max); + } + + private void setBubbleAndHandlePosition(float y) { + int bubbleHeight = bubble.getHeight(); + int handleHeight = handle.getHeight(); + handle.setY(getValueInRange(0,height-handleHeight,(int)(y-handleHeight/2))); + bubble.setY(getValueInRange(0,height-bubbleHeight-handleHeight/2,(int)(y-bubbleHeight))); + } + + private void showBubble() { + AnimatorSet animatorSet = new AnimatorSet(); + bubble.setVisibility(VISIBLE); + if(currentAnimator != null) + currentAnimator.cancel(); + currentAnimator = ObjectAnimator.ofFloat(bubble,"alpha",0f,1f).setDuration(BUBBLE_ANIMATION_DURATION); + currentAnimator.start(); + } + + private void hideBubble() { + if(currentAnimator != null) + currentAnimator.cancel(); + currentAnimator = ObjectAnimator.ofFloat(bubble,"alpha",1f,0f).setDuration(BUBBLE_ANIMATION_DURATION); + currentAnimator.addListener(new AnimatorListenerAdapter(){ + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + bubble.setVisibility(INVISIBLE); + currentAnimator = null; + } + + @Override + public void onAnimationCancel(Animator animation) { + super.onAnimationCancel(animation); + bubble.setVisibility(INVISIBLE); + currentAnimator = null; + } + }); + currentAnimator.start(); + } + + private class ScrollListener extends OnScrollListener { + @Override + public void onScrolled(RecyclerView rv,int dx,int dy) { + View firstVisibleView = recyclerView.getChildAt(0); + int firstVisiblePosition = recyclerView.getChildPosition(firstVisibleView); + int visibleRange = recyclerView.getChildCount(); + int lastVisiblePosition = firstVisiblePosition+visibleRange; + int itemCount = recyclerView.getAdapter().getItemCount(); + int position; + if(firstVisiblePosition == 0) + position = 0; + else if(lastVisiblePosition == itemCount) + position = itemCount; + else + position = (int)(((float)firstVisiblePosition/(((float)itemCount-(float)visibleRange)))*(float)itemCount); + float proportion = (float)position/(float)itemCount; + setBubbleAndHandlePosition(height*proportion); + } + } + + public interface BubbleTextGetter { + String getTextToShowInBubble(int position); + } +} diff --git a/app/src/main/res/drawable/fast_scroller_bubble.xml b/app/src/main/res/drawable/fast_scroller_bubble.xml new file mode 100644 index 00000000..02dfee5b --- /dev/null +++ b/app/src/main/res/drawable/fast_scroller_bubble.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + + <corners + android:topLeftRadius="44dp" + android:topRightRadius="44dp" + android:bottomLeftRadius="44dp" + android:bottomRightRadius="0px"/> + + <solid android:color="#FF0288D1"/> + + <size + android:height="88dp" + android:width="88dp"/> +</shape>
\ No newline at end of file diff --git a/app/src/main/res/drawable/fast_scroller_handle.xml b/app/src/main/res/drawable/fast_scroller_handle.xml new file mode 100644 index 00000000..e1744ceb --- /dev/null +++ b/app/src/main/res/drawable/fast_scroller_handle.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_selected="true"> + <shape android:shape="rectangle"> + <corners android:radius="2dp"/> + + <solid android:color="#FF0288D1"/> + + <size + android:height="32dp" + android:width="4dp"/> + </shape> + </item> + + <item> + <shape android:shape="rectangle"> + <corners android:radius="2dp"/> + + <solid android:color="#FF737373"/> + + <size + android:height="32dp" + android:width="4dp"/> + </shape> + </item> +</selector>
\ No newline at end of file diff --git a/app/src/main/res/layout/abstract_recycler_fragment.xml b/app/src/main/res/layout/abstract_recycler_fragment.xml index 47141450..0e0c87f4 100644 --- a/app/src/main/res/layout/abstract_recycler_fragment.xml +++ b/app/src/main/res/layout/abstract_recycler_fragment.xml @@ -10,12 +10,23 @@ android:layout_height="fill_parent" android:orientation="vertical" > - <android.support.v7.widget.RecyclerView - android:id="@+id/fragment_recycler" + <RelativeLayout android:layout_width="fill_parent" android:layout_height="0dip" - android:layout_weight="1.0" - android:scrollbars="vertical"/> + android:layout_weight="1.0"> + + <android.support.v7.widget.RecyclerView + android:id="@+id/fragment_recycler" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:scrollbars="vertical"/> + + <github.daneren2005.dsub.view.FastScroller + android:id="@+id/fragment_fast_scroller" + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:layout_alignParentRight="true"/> + </RelativeLayout> <include layout="@layout/tab_progress" /> </LinearLayout> diff --git a/app/src/main/res/layout/fast_scroller.xml b/app/src/main/res/layout/fast_scroller.xml new file mode 100644 index 00000000..b2e244e3 --- /dev/null +++ b/app/src/main/res/layout/fast_scroller.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="wrap_content" + android:layout_height="match_parent"> + + <TextView + android:id="@+id/fastscroller_bubble" + android:layout_gravity="right|end" + android:gravity="center" + android:textSize="48sp" tools:text="A" + android:layout_width="wrap_content" + android:textColor="#FFffffff" + android:layout_height="wrap_content" + android:background="@drawable/fast_scroller_bubble" + android:visibility="visible"/> + + <ImageView + android:id="@+id/fastscroller_handle" + android:layout_width="wrap_content" + android:layout_marginRight="8dp" + android:layout_marginLeft="8dp" + android:layout_height="wrap_content" + android:src="@drawable/fast_scroller_handle"/> +</merge>
\ No newline at end of file |