squash-merge of slide-up branch

This commit is contained in:
Adrian Ulrich 2016-03-20 13:21:25 +01:00
parent 563b01ee6c
commit 1a6774ca2f
8 changed files with 677 additions and 64 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View 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>

View File

@ -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) {

View File

@ -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();

View 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) {
}
}

View 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);
}
}
}