squash-merge of slide-up branch
This commit is contained in:
parent
563b01ee6c
commit
1a6774ca2f
@ -20,7 +20,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
-->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:vanilla="http://schemas.android.com/apk/res/ch.blinkenlights.android.vanilla" >
|
||||
<ch.blinkenlights.android.vanilla.CoverView
|
||||
android:id="@+id/cover_view"
|
||||
android:layout_height="fill_parent"
|
||||
@ -55,14 +56,28 @@ THE SOFTWARE.
|
||||
android:layout_gravity="center"
|
||||
android:paddingRight="5dip" />
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/controls_bottom"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
<ch.blinkenlights.android.vanilla.SlidingView
|
||||
android:id="@+id/sliding_view"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_gravity="bottom|left"
|
||||
android:layout_margin="0dip"
|
||||
android:background="?overlay_background_color"
|
||||
android:orientation="horizontal">
|
||||
<include layout="@layout/controls" />
|
||||
</LinearLayout>
|
||||
android:layout_marginTop="36dip"
|
||||
android:orientation="horizontal"
|
||||
vanilla:slider_handle_id="@+id/queue_slider">
|
||||
<LinearLayout
|
||||
android:id="@+id/queue_slider"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:background="?overlay_background_color"
|
||||
android:orientation="horizontal">
|
||||
<include layout="@layout/controls" />
|
||||
</LinearLayout>
|
||||
<fragment class="ch.blinkenlights.android.vanilla.ShowQueueFragment"
|
||||
android:id="@+id/queue"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" />
|
||||
</ch.blinkenlights.android.vanilla.SlidingView>
|
||||
|
||||
|
||||
</merge>
|
||||
|
@ -20,7 +20,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
-->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:vanilla="http://schemas.android.com/apk/res/ch.blinkenlights.android.vanilla" >
|
||||
<ch.blinkenlights.android.vanilla.CoverView
|
||||
android:id="@+id/cover_view"
|
||||
android:layout_height="fill_parent"
|
||||
@ -139,14 +140,29 @@ THE SOFTWARE.
|
||||
android:layout_gravity="center" />
|
||||
</LinearLayout>
|
||||
</TableLayout>
|
||||
<LinearLayout
|
||||
android:id="@+id/controls_bottom"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
|
||||
<ch.blinkenlights.android.vanilla.SlidingView
|
||||
android:id="@+id/sliding_view"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_gravity="bottom|left"
|
||||
android:layout_margin="0dip"
|
||||
android:background="?overlay_background_color"
|
||||
android:orientation="horizontal">
|
||||
<include layout="@layout/controls" />
|
||||
</LinearLayout>
|
||||
android:layout_marginTop="88dip"
|
||||
android:orientation="horizontal"
|
||||
vanilla:slider_handle_id="@+id/queue_slider">
|
||||
<LinearLayout
|
||||
android:id="@+id/queue_slider"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:background="?overlay_background_color"
|
||||
android:orientation="horizontal">
|
||||
<include layout="@layout/controls" />
|
||||
</LinearLayout>
|
||||
<fragment class="ch.blinkenlights.android.vanilla.ShowQueueFragment"
|
||||
android:id="@+id/queue"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" />
|
||||
</ch.blinkenlights.android.vanilla.SlidingView>
|
||||
|
||||
|
||||
</merge>
|
||||
|
@ -20,36 +20,63 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/content"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<android.support.iosched.tabs.VanillaTabLayout
|
||||
android:id="@+id/sliding_tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="4dp"
|
||||
android:background="@color/tabs_background" />
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/pager"
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:vanilla="http://schemas.android.com/apk/res/ch.blinkenlights.android.vanilla" >
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/content"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="0px"
|
||||
android:layout_weight="1" />
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/limiter_scroller"
|
||||
android:layout_height="fill_parent">
|
||||
<android.support.iosched.tabs.VanillaTabLayout
|
||||
android:id="@+id/sliding_tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="4dp"
|
||||
android:background="@color/tabs_background" />
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/pager"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="0px"
|
||||
android:layout_weight="1" />
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/limiter_scroller"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|left">
|
||||
<LinearLayout
|
||||
android:id="@+id/limiter_layout"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="fill_parent" />
|
||||
</HorizontalScrollView>
|
||||
</LinearLayout>
|
||||
<ch.blinkenlights.android.vanilla.SlidingView
|
||||
android:id="@+id/sliding_view"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|left">
|
||||
<LinearLayout
|
||||
android:id="@+id/limiter_layout"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="fill_parent" />
|
||||
</HorizontalScrollView>
|
||||
<include layout="@layout/bottombar_controls" android:id="@+id/bottombar_controls" />
|
||||
</LinearLayout>
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_gravity="bottom|left"
|
||||
android:orientation="horizontal"
|
||||
vanilla:slider_handle_id="@+id/bottombar_controls"
|
||||
vanilla:slider_slave_id="@+id/content">
|
||||
<LinearLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:background="?overlay_background_color"
|
||||
android:orientation="horizontal">
|
||||
<include layout="@layout/bottombar_controls" android:id="@+id/bottombar_controls" />
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="fill_parent"
|
||||
android:background="?overlay_background_color"
|
||||
android:orientation="horizontal">
|
||||
<include layout="@layout/controls" />
|
||||
</LinearLayout>
|
||||
|
||||
<fragment class="ch.blinkenlights.android.vanilla.ShowQueueFragment"
|
||||
android:id="@+id/queue"
|
||||
android:layout_width="fill_parent" android:layout_height="fill_parent" />
|
||||
</ch.blinkenlights.android.vanilla.SlidingView>
|
||||
</merge>
|
||||
|
7
res/values/slidingview_attr.xml
Normal file
7
res/values/slidingview_attr.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="SlidingViewPreferences">
|
||||
<attr name="slider_handle_id" format="integer"/>
|
||||
<attr name="slider_slave_id" format="integer"/>
|
||||
</declare-styleable>
|
||||
</resources>
|
@ -64,7 +64,7 @@ public class FullPlaybackActivity extends PlaybackActivity
|
||||
|
||||
private TextView mOverlayText;
|
||||
private View mControlsTop;
|
||||
private View mControlsBottom;
|
||||
private View mSlidingView;
|
||||
|
||||
private SeekBar mSeekBar;
|
||||
private TableLayout mInfoTable;
|
||||
@ -161,7 +161,7 @@ public class FullPlaybackActivity extends PlaybackActivity
|
||||
coverView.setOnLongClickListener(this);
|
||||
mCoverView = coverView;
|
||||
|
||||
mControlsBottom = findViewById(R.id.controls_bottom);
|
||||
mSlidingView = findViewById(R.id.sliding_view);
|
||||
View previousButton = findViewById(R.id.previous);
|
||||
previousButton.setOnClickListener(this);
|
||||
mPlayPauseButton = (ImageButton)findViewById(R.id.play_pause);
|
||||
@ -389,13 +389,13 @@ public class FullPlaybackActivity extends PlaybackActivity
|
||||
openLibrary(null);
|
||||
break;
|
||||
case MENU_ENQUEUE_ALBUM:
|
||||
PlaybackService.get(this).enqueueFromCurrent(MediaUtils.TYPE_ALBUM);
|
||||
PlaybackService.get(this).enqueueFromSong(PlaybackService.get(this).getSong(0), MediaUtils.TYPE_ALBUM);
|
||||
break;
|
||||
case MENU_ENQUEUE_ARTIST:
|
||||
PlaybackService.get(this).enqueueFromCurrent(MediaUtils.TYPE_ARTIST);
|
||||
PlaybackService.get(this).enqueueFromSong(PlaybackService.get(this).getSong(0), MediaUtils.TYPE_ARTIST);
|
||||
break;
|
||||
case MENU_ENQUEUE_GENRE:
|
||||
PlaybackService.get(this).enqueueFromCurrent(MediaUtils.TYPE_GENRE);
|
||||
PlaybackService.get(this).enqueueFromSong(PlaybackService.get(this).getSong(0), MediaUtils.TYPE_GENRE);
|
||||
break;
|
||||
case MENU_SONG_FAVORITE:
|
||||
Song song = (PlaybackService.get(this)).getSong(0);
|
||||
@ -513,7 +513,7 @@ public class FullPlaybackActivity extends PlaybackActivity
|
||||
{
|
||||
int mode = visible ? View.VISIBLE : View.GONE;
|
||||
mControlsTop.setVisibility(mode);
|
||||
mControlsBottom.setVisibility(mode);
|
||||
mSlidingView.setVisibility(mode);
|
||||
mControlsVisible = visible;
|
||||
|
||||
if (visible) {
|
||||
|
@ -1777,37 +1777,37 @@ public final class PlaybackService extends Service
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues all the songs with the same album/artist/genre as the current
|
||||
* Enqueues all the songs with the same album/artist/genre as the passed
|
||||
* song.
|
||||
*
|
||||
* This will clear the queue and place the first song from the group after
|
||||
* the playing song.
|
||||
*
|
||||
* @param song The song to base the query on
|
||||
* @param type The media type, one of MediaUtils.TYPE_ALBUM, TYPE_ARTIST,
|
||||
* or TYPE_GENRE
|
||||
*/
|
||||
public void enqueueFromCurrent(int type)
|
||||
public void enqueueFromSong(Song song, int type)
|
||||
{
|
||||
Song current = mCurrentSong;
|
||||
if (current == null)
|
||||
if (song == null)
|
||||
return;
|
||||
|
||||
long id;
|
||||
switch (type) {
|
||||
case MediaUtils.TYPE_ARTIST:
|
||||
id = current.artistId;
|
||||
id = song.artistId;
|
||||
break;
|
||||
case MediaUtils.TYPE_ALBUM:
|
||||
id = current.albumId;
|
||||
id = song.albumId;
|
||||
break;
|
||||
case MediaUtils.TYPE_GENRE:
|
||||
id = MediaUtils.queryGenreForSong(getContentResolver(), current.id);
|
||||
id = MediaUtils.queryGenreForSong(getContentResolver(), song.id);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported media type: " + type);
|
||||
}
|
||||
|
||||
String selection = "_id!=" + current.id;
|
||||
String selection = "_id!=" + song.id;
|
||||
QueryTask query = MediaUtils.buildQuery(type, id, Song.FILLED_PROJECTION, selection);
|
||||
query.mode = SongTimeline.MODE_FLUSH_AND_PLAY_NEXT;
|
||||
addSongs(query);
|
||||
@ -2248,13 +2248,13 @@ public final class PlaybackService extends Service
|
||||
break;
|
||||
}
|
||||
case EnqueueAlbum:
|
||||
enqueueFromCurrent(MediaUtils.TYPE_ALBUM);
|
||||
enqueueFromSong(mCurrentSong, MediaUtils.TYPE_ALBUM);
|
||||
break;
|
||||
case EnqueueArtist:
|
||||
enqueueFromCurrent(MediaUtils.TYPE_ARTIST);
|
||||
enqueueFromSong(mCurrentSong, MediaUtils.TYPE_ARTIST);
|
||||
break;
|
||||
case EnqueueGenre:
|
||||
enqueueFromCurrent(MediaUtils.TYPE_GENRE);
|
||||
enqueueFromSong(mCurrentSong, MediaUtils.TYPE_GENRE);
|
||||
break;
|
||||
case ClearQueue:
|
||||
clearQueue();
|
||||
|
233
src/ch/blinkenlights/android/vanilla/ShowQueueFragment.java
Normal file
233
src/ch/blinkenlights/android/vanilla/ShowQueueFragment.java
Normal file
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Adrian Ulrich <adrian@blinkenlights.ch>
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ch.blinkenlights.android.vanilla;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import com.mobeta.android.dslv.DragSortListView;
|
||||
|
||||
|
||||
public class ShowQueueFragment extends Fragment
|
||||
implements TimelineCallback,
|
||||
AdapterView.OnItemClickListener,
|
||||
DragSortListView.DropListener,
|
||||
DragSortListView.RemoveListener,
|
||||
MenuItem.OnMenuItemClickListener
|
||||
{
|
||||
|
||||
private DragSortListView mListView;
|
||||
private ShowQueueAdapter mListAdapter;
|
||||
private PlaybackService mService;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
|
||||
View view = inflater.inflate(R.layout.showqueue_listview, container, false);
|
||||
Context context = getActivity();
|
||||
|
||||
mListView = (DragSortListView) view.findViewById(R.id.list);
|
||||
mListAdapter = new ShowQueueAdapter(context, R.layout.draggable_row);
|
||||
mListView.setAdapter(mListAdapter);
|
||||
mListView.setDropListener(this);
|
||||
mListView.setRemoveListener(this);
|
||||
mListView.setOnItemClickListener(this);
|
||||
mListView.setOnCreateContextMenuListener(this);
|
||||
|
||||
PlaybackService.addTimelineCallback(this);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
PlaybackService.removeTimelineCallback(this);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// Check if playback service has already been created
|
||||
if (mService == null && PlaybackService.hasInstance())
|
||||
mService = PlaybackService.get(getActivity());
|
||||
|
||||
if (mService != null)
|
||||
refreshSongQueueList(true);
|
||||
}
|
||||
|
||||
|
||||
private final static int MENU_PLAY = 100;
|
||||
private final static int MENU_ENQUEUE_ALBUM = 101;
|
||||
private final static int MENU_ENQUEUE_ARTIST = 102;
|
||||
private final static int MENU_ENQUEUE_GENRE = 103;
|
||||
private final static int MENU_REMOVE = 104;
|
||||
|
||||
/**
|
||||
* Called by Android on long press. Builds the long press context menu.
|
||||
*/
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View listView, ContextMenu.ContextMenuInfo absInfo) {
|
||||
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)absInfo;
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra("id", info.id);
|
||||
intent.putExtra("position", info.position);
|
||||
Song song = mService.getSongByQueuePosition(info.position);
|
||||
|
||||
menu.setHeaderTitle(song.title);
|
||||
menu.add(0, MENU_PLAY, 0, R.string.play).setIntent(intent).setOnMenuItemClickListener(this);
|
||||
menu.add(0, MENU_ENQUEUE_ALBUM, 0, R.string.enqueue_current_album).setIntent(intent).setOnMenuItemClickListener(this);
|
||||
menu.add(0, MENU_ENQUEUE_ARTIST, 0, R.string.enqueue_current_artist).setIntent(intent).setOnMenuItemClickListener(this);
|
||||
menu.add(0, MENU_ENQUEUE_GENRE, 0, R.string.enqueue_current_genre).setIntent(intent).setOnMenuItemClickListener(this);
|
||||
menu.add(0, MENU_REMOVE, 0, R.string.remove).setIntent(intent).setOnMenuItemClickListener(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by Android after the User selected a MenuItem.
|
||||
*
|
||||
* @param item The selected menu item.
|
||||
*/
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
Intent intent = item.getIntent();
|
||||
int itemId = item.getItemId();
|
||||
int pos = intent.getIntExtra("position", -1);
|
||||
|
||||
Song song = mService.getSongByQueuePosition(pos);
|
||||
switch (item.getItemId()) {
|
||||
case MENU_PLAY:
|
||||
onItemClick(null, null, pos, -1);
|
||||
break;
|
||||
case MENU_ENQUEUE_ALBUM:
|
||||
mService.enqueueFromSong(song, MediaUtils.TYPE_ALBUM);
|
||||
break;
|
||||
case MENU_ENQUEUE_ARTIST:
|
||||
mService.enqueueFromSong(song, MediaUtils.TYPE_ARTIST);
|
||||
break;
|
||||
case MENU_ENQUEUE_GENRE:
|
||||
mService.enqueueFromSong(song, MediaUtils.TYPE_GENRE);
|
||||
break;
|
||||
case MENU_REMOVE:
|
||||
remove(pos);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unhandled menu id received!");
|
||||
// we could actually dispatch this to the hosting activity, but we do not need this for now.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fired from adapter listview if user moved an item
|
||||
* @param from the item index that was dragged
|
||||
* @param to the index where the item was dropped
|
||||
*/
|
||||
@Override
|
||||
public void drop(int from, int to) {
|
||||
if (from != to) {
|
||||
mService.moveSongPosition(from, to);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired from adapter listview after user removed a song
|
||||
* @param which index to remove from queue
|
||||
*/
|
||||
@Override
|
||||
public void remove(int which) {
|
||||
mService.removeSongPosition(which);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an item in the listview gets clicked
|
||||
*/
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
mService.jumpToQueuePosition(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a refresh of the queueview
|
||||
* @param scroll enable or disable jumping to the currently playing item
|
||||
*/
|
||||
public void refreshSongQueueList(final boolean scroll) {
|
||||
getActivity().runOnUiThread(new Runnable(){
|
||||
public void run() {
|
||||
int i, stotal, spos;
|
||||
stotal = mService.getTimelineLength(); /* Total number of songs in queue */
|
||||
spos = mService.getTimelinePosition(); /* Current position in queue */
|
||||
|
||||
mListAdapter.clear(); /* Flush all existing entries... */
|
||||
mListAdapter.highlightRow(spos); /* and highlight current position */
|
||||
|
||||
for(i=0 ; i<stotal; i++) {
|
||||
mListAdapter.add(mService.getSongByQueuePosition(i));
|
||||
}
|
||||
|
||||
if(scroll)
|
||||
scrollToCurrentSong(spos);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls to the current song<br/>
|
||||
* We suppress the new api lint check as lint thinks
|
||||
* {@link android.widget.AbsListView#setSelectionFromTop(int, int)} was only added in
|
||||
* {@link Build.VERSION_CODES#JELLY_BEAN}, but it was actually added in API
|
||||
* level 1<br/>
|
||||
* <a href="https://developer.android.com/reference/android/widget/AbsListView.html#setSelectionFromTop%28int,%20int%29">
|
||||
* Android reference: AbsListView.setSelectionFromTop()</a>
|
||||
* @param currentSongPosition The position in {@link #mListView} of the current song
|
||||
*/
|
||||
@SuppressLint("NewApi")
|
||||
private void scrollToCurrentSong(int currentSongPosition){
|
||||
mListView.setSelectionFromTop(currentSongPosition, 0); /* scroll to currently playing song */
|
||||
}
|
||||
|
||||
// Used Callbacks of TImelineCallback
|
||||
public void onTimelineChanged() {
|
||||
if (mService == null)
|
||||
mService = PlaybackService.get(getActivity());
|
||||
refreshSongQueueList(false);
|
||||
}
|
||||
|
||||
// Unused Callbacks of TimelineCallback
|
||||
public void onPositionInfoChanged() {
|
||||
}
|
||||
public void onMediaChange() {
|
||||
}
|
||||
public void recreate() {
|
||||
}
|
||||
public void replaceSong(int delta, Song song) {
|
||||
}
|
||||
public void setSong(long uptime, Song song) {
|
||||
}
|
||||
public void setState(long uptime, int state) {
|
||||
}
|
||||
}
|
315
src/ch/blinkenlights/android/vanilla/SlidingView.java
Normal file
315
src/ch/blinkenlights/android/vanilla/SlidingView.java
Normal file
@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Adrian Ulrich <adrian@blinkenlights.ch>
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package ch.blinkenlights.android.vanilla;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class SlidingView extends FrameLayout
|
||||
implements View.OnTouchListener
|
||||
{
|
||||
/**
|
||||
* Ignore drag until we made 30 px progress.
|
||||
*/
|
||||
private final float MAX_PROGRESS = 30;
|
||||
/**
|
||||
* The maximum (initial) offset of the view
|
||||
*/
|
||||
private float mMaxOffsetY = 0;
|
||||
/**
|
||||
* The previous Y coordinate, used to calculate the movement diff.
|
||||
*/
|
||||
private float mPreviousY = 0;
|
||||
/**
|
||||
* The total progress in pixels of this drag
|
||||
*/
|
||||
private float mProgressPx = 0;
|
||||
/**
|
||||
* Signals the direction of the fling
|
||||
*/
|
||||
private int mFlingDirection = 0;
|
||||
/**
|
||||
* TRUE if we started to move this view
|
||||
*/
|
||||
private boolean mDidScroll = false;
|
||||
/**
|
||||
* Reference to the gesture detector
|
||||
*/
|
||||
private GestureDetector mDetector;
|
||||
/**
|
||||
* An external View we are managing during layout changes.
|
||||
*/
|
||||
private View mSlaveView;
|
||||
/**
|
||||
* The resource id to listen for touch events
|
||||
*/
|
||||
private int mSliderHandleId = 0;
|
||||
/**
|
||||
* The current expansion stage
|
||||
*/
|
||||
int mCurrentStage = 0;
|
||||
/**
|
||||
* List with all possible stages and their offsets
|
||||
*/
|
||||
ArrayList<Integer> mStages = new ArrayList<Integer>();
|
||||
|
||||
|
||||
public SlidingView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public SlidingView(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public SlidingView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
setBackgroundColor(ThemeHelper.getDefaultCoverColors(context)[0]);
|
||||
|
||||
mDetector = new GestureDetector(new GestureListener());
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingViewPreferences);
|
||||
mSliderHandleId = a.getResourceId(R.styleable.SlidingViewPreferences_slider_handle_id, 0);
|
||||
int slaveId = a.getResourceId(R.styleable.SlidingViewPreferences_slider_slave_id, 0);
|
||||
a.recycle();
|
||||
|
||||
// This is probably a parent view: so we need the context but can search
|
||||
// it before we got inflated:
|
||||
mSlaveView = ((Activity)context).findViewById(slaveId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fully expands the slide
|
||||
*/
|
||||
public void expandSlide() {
|
||||
setExpansionStage(mStages.size()-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the slide
|
||||
*/
|
||||
public void hideSlide() {
|
||||
setExpansionStage(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms to the new expansion state
|
||||
*
|
||||
* @param stage the stage to transform to
|
||||
*/
|
||||
private void setExpansionStage(int stage) {
|
||||
mCurrentStage = stage;
|
||||
int pxOff = mStages.get(stage);
|
||||
this
|
||||
.animate()
|
||||
.translationY(pxOff)
|
||||
.setListener(new AnimationListener())
|
||||
.setInterpolator(new DecelerateInterpolator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the parent view to fit given stage
|
||||
*
|
||||
* @param stage the stage to transform to
|
||||
*/
|
||||
private void setSlaveViewStage(int stage) {
|
||||
if (mSlaveView == null)
|
||||
return;
|
||||
|
||||
int totalOffset = 0;
|
||||
for (int i = 0; i <= stage; i++) {
|
||||
totalOffset += getChildAt(i).getHeight();
|
||||
}
|
||||
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams)mSlaveView.getLayoutParams();
|
||||
params.bottomMargin = totalOffset;
|
||||
mSlaveView.setLayoutParams(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the view was inflated, binds an onTouchListener to all child
|
||||
* elements of the child view
|
||||
*/
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
View handle = findViewById(mSliderHandleId);
|
||||
|
||||
if (handle != null) {
|
||||
if (handle instanceof ViewGroup) {
|
||||
ViewGroup group = (ViewGroup)handle;
|
||||
for (int i = 0; i < group.getChildCount(); i++) {
|
||||
group.getChildAt(i).setOnTouchListener(this);
|
||||
}
|
||||
} else {
|
||||
handle.setOnTouchListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to stack all views orizontally in the available space
|
||||
*/
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
|
||||
int viewHeight = getMeasuredHeight();
|
||||
int childCount = getChildCount();
|
||||
int topOffset = 0;
|
||||
View lastChild = null;
|
||||
|
||||
mStages.clear();
|
||||
|
||||
for (int i = 0; i < childCount ; i++) {
|
||||
lastChild = getChildAt(i);
|
||||
int childWidth = lastChild.getMeasuredWidth();
|
||||
int childHeight = lastChild.getMeasuredHeight();
|
||||
int childBottom = childHeight + topOffset;
|
||||
|
||||
// No child should consume space outside of our view
|
||||
if (topOffset > viewHeight)
|
||||
topOffset = viewHeight;
|
||||
if (childBottom > viewHeight)
|
||||
childBottom = viewHeight;
|
||||
|
||||
lastChild.layout(0, topOffset, childWidth, childBottom);
|
||||
mStages.add(viewHeight - childBottom);
|
||||
topOffset += childHeight;
|
||||
}
|
||||
|
||||
if (lastChild != null && mMaxOffsetY == 0) {
|
||||
// Sizes are now fixed: Overwrite any (possible) FILL_PARENT or WRAP_CONTENT
|
||||
// value with the measured size
|
||||
// This should only happen on the first run (mMaxOffsetY == 0)
|
||||
for (int i = 0; i < childCount ; i++) {
|
||||
View child = getChildAt(i);
|
||||
FrameLayout.LayoutParams params = (FrameLayout.LayoutParams)child.getLayoutParams();
|
||||
params.height = child.getHeight();
|
||||
params.width = child.getWidth();
|
||||
child.setLayoutParams(params);
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
mMaxOffsetY = mStages.get(0);
|
||||
setTranslationY(mMaxOffsetY);
|
||||
setExpansionStage(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event){
|
||||
// Fix up the event offset as we are moving the view itself.
|
||||
// This is required to get flings correctly detected
|
||||
event.setLocation(event.getRawX(), event.getRawY());
|
||||
|
||||
mDetector.onTouchEvent(event);
|
||||
float y = event.getRawY();
|
||||
float dy = y - mPreviousY; // diff Y
|
||||
float vy = getTranslationY(); // view Y
|
||||
|
||||
switch(event.getActionMasked()) {
|
||||
case MotionEvent.ACTION_UP : {
|
||||
if (mDidScroll == false) { // Dispatch event if we never scrolled
|
||||
v.onTouchEvent(event);
|
||||
} else {
|
||||
int nstages = mStages.size();
|
||||
int tstage = 0;
|
||||
int tbonus = (int)mProgressPx * mFlingDirection; // we add the progress as virtual bonus on fling
|
||||
int toff = (int)mMaxOffsetY;
|
||||
for (int i=0; i<nstages; i++) {
|
||||
int tdiff = Math.abs((int)vy + tbonus - mStages.get(i));
|
||||
if (tdiff < toff) {
|
||||
toff = tdiff;
|
||||
tstage = i;
|
||||
}
|
||||
}
|
||||
setExpansionStage(tstage);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_DOWN : {
|
||||
v.onTouchEvent(event);
|
||||
|
||||
mProgressPx = 0;
|
||||
mFlingDirection = 0;
|
||||
mDidScroll = false;
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE : {
|
||||
mProgressPx += Math.abs(dy);
|
||||
float usedY = vy + dy;
|
||||
|
||||
if (usedY < 0)
|
||||
usedY = 0;
|
||||
if (usedY > mMaxOffsetY)
|
||||
usedY = mMaxOffsetY;
|
||||
|
||||
if (mProgressPx < MAX_PROGRESS) {
|
||||
// we did not reach a minimum of progress: do not scroll yet
|
||||
usedY = vy;
|
||||
} else if (mDidScroll == false) {
|
||||
mDidScroll = true;
|
||||
event.setAction(MotionEvent.ACTION_CANCEL);
|
||||
v.onTouchEvent(event);
|
||||
setSlaveViewStage(0); // parent can use full view, will be reset on ACTION_UP handlers
|
||||
}
|
||||
|
||||
setTranslationY(usedY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mPreviousY = y;
|
||||
return true;
|
||||
}
|
||||
|
||||
class GestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||
@Override
|
||||
public boolean onFling(MotionEvent event1, MotionEvent event2, float velocityX, float velocityY) {
|
||||
mFlingDirection = (velocityY > 0 ? 1 : -1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class AnimationListener extends AnimatorListenerAdapter {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
setSlaveViewStage(mCurrentStage);
|
||||
}
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
onAnimationEnd(animation);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user