Skip to content

Commit

Permalink
started work on over flipping modes as well as stable id support
Browse files Browse the repository at this point in the history
  • Loading branch information
emilsjolander committed Jun 21, 2013
1 parent cd39737 commit 1b39e6e
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 26 deletions.
153 changes: 140 additions & 13 deletions library/src/se/emilsjolander/flipview/FlipView.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,20 @@

public class FlipView extends FrameLayout {

public enum OverFlipMode {
GLOW, RUBBER_BAND
}

public interface OnFlipListener {
public void onFlippedToPage(FlipView v, int position, long id);
}

public interface OnOverFlipListener {
public void onOverFlip(FlipView v, OverFlipMode mode,
boolean overFlipTop, float overFlipDistance,
float flipDistancePerPage);
}

// wrapper class used when keeping track of active views
static class Page {
int position;
Expand All @@ -50,6 +60,9 @@ public Page(int position, View view) {
}
}

private static final float MAX_OVER_FLIP_DISTANCE = 60;// only applies to
// rubber band
// overflip mode
private static final int PEAK_ANIM_DURATION = 600;// in ms
private static final int MAX_SINGLE_PAGE_FLIP_ANIM_DURATION = 300;// in ms
private static final int FLIP_DISTANCE_PER_PAGE = 180; // for normalizing
Expand All @@ -58,6 +71,11 @@ public Page(int position, View view) {
private static final int MAX_SHADE_ALPHA = 130;// out of 255
private static final int MAX_SHINE_ALPHA = 100;// out of 255
private static final int INVALID_POINTER = -1;// value for no pointer
private static final int VERTICAL_FLIP = 0;// constant used by the
// attributes
@SuppressWarnings("unused")
private static final int HORIZONTAL_FLIP = 1;// constant used by the
// attributes

private DataSetObserver dataSetObserver = new DataSetObserver() {

Expand Down Expand Up @@ -98,15 +116,20 @@ public void onInvalidated() {
// views get recycled after they have been pushed out of the active queue
private Recycler mRecycler = new Recycler();

// holds all views that are currently in use, hold max of 3 views
// holds all views that are currently in use, hold max of 3 views as of now
private Queue<Page> mActivePageQueue = new LinkedList<FlipView.Page>();

private ListAdapter mAdapter;
private int mPageCount = 0;

private OnFlipListener mOnFlipListener;
private OnOverFlipListener mOnOverFlipListener;

private float mFlipDistance = 0;
private int mCurrentPage = 0;
private long mCurrentPageId = 0;

private OverFlipMode mOverFlipMode = OverFlipMode.GLOW;

// clipping rects
private Rect mTopRect = new Rect();
Expand Down Expand Up @@ -142,7 +165,8 @@ public FlipView(Context context, AttributeSet attrs, int defStyle) {
R.styleable.FlipView);

// 0 is vertical, 1 is horizontal
mIsFlippingVertically = a.getInt(R.styleable.FlipView_orientation, 0) < 1;
mIsFlippingVertically = a.getInt(R.styleable.FlipView_orientation,
VERTICAL_FLIP) == VERTICAL_FLIP;

a.recycle();

Expand All @@ -169,13 +193,40 @@ private void init() {
}

private void dataSetChanged() {
// if the adapter has stable ids, try to keep the page currently on
// stable.
if (mAdapter.hasStableIds()) {
mCurrentPage = getNewPositionOfCurrentPage();
}
mCurrentPageId = mAdapter.getItemId(mCurrentPage);

mPageCount = mAdapter.getCount();
removeAllViews();
mActivePageQueue.clear();
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
addView(viewForPage(mCurrentPage));
}

private int getNewPositionOfCurrentPage() {
// check if id is on same position, this is because it will
// often be that and this way you do not need to iterate the whole
// dataset. If it is the same position, you are done.
if(mCurrentPageId == mAdapter.getItemId(mCurrentPage)) {
return mCurrentPage;
}

// iterate the dataset and look for the correct id. If it
// exists, set that position as the current position.
for(int i = 0 ; i<mAdapter.getCount() ; i++) {
if(mCurrentPageId == mAdapter.getItemId(i)) {
return i;
}
}

//Id no longer is dataset, keep current page
return mCurrentPage;
}

private void dataSetInvalidated() {
if (mAdapter != null) {
mAdapter.unregisterDataSetObserver(dataSetObserver);
Expand Down Expand Up @@ -416,14 +467,8 @@ public boolean onTouchEvent(MotionEvent ev) {
mFlipDistance += deltaFlipDistance;

float distanceBound = bindFlipDistance();
if (distanceBound > 0) {
mNextEdgeEffect.onPull(distanceBound
/ (isFlippingVertically() ? getHeight()
: getWidth()));
} else if (distanceBound < 0) {
mPreviousEdgeEffect.onPull(-distanceBound
/ (isFlippingVertically() ? getHeight()
: getWidth()));
if (distanceBound != 0) {
performOverFlip(distanceBound);
}
invalidate();
}
Expand Down Expand Up @@ -505,18 +550,53 @@ protected void dispatchDraw(Canvas canvas) {
postAddView(v);
postFlippedToPage(currentPage);
mCurrentPage = currentPage;
mCurrentPageId = mAdapter.getItemId(mCurrentPage);
}
setDrawWithLayer(v, false);
v.draw(canvas);
}

needsInvalidate |= drawEdgeEffects(canvas);
// if overflip is GLOW mode and the edge effects needed drawing, make
// sure to invalidate
needsInvalidate |= mOverFlipMode == OverFlipMode.GLOW
&& drawEdgeEffects(canvas);

if (needsInvalidate) {
// always invalidate whole screen as it is needed 99% of the time.
// This is because of the shadows and shines put on the non-flipping
// pages
invalidate();
}
}

private void performOverFlip(float distanceBound) {
switch (mOverFlipMode) {
case GLOW:
if (distanceBound > 0) {
mNextEdgeEffect.onPull(distanceBound
/ (isFlippingVertically() ? getHeight() : getWidth()));
} else if (distanceBound < 0) {
mPreviousEdgeEffect.onPull(-distanceBound
/ (isFlippingVertically() ? getHeight() : getWidth()));
}
break;

case RUBBER_BAND:
float overFlip = (float) (1 + Math.pow(Math.abs(distanceBound), 0.8));
overFlip = Math.min(overFlip, MAX_OVER_FLIP_DISTANCE);
mFlipDistance += Math.signum(distanceBound) * overFlip;
break;
}

// notify the listener of an over flip. This is great if wanting to
// implement pull-to-refresh
if (mOnOverFlipListener != null) {
mOnOverFlipListener.onOverFlip(this, mOverFlipMode,
Math.signum(distanceBound) < 0, Math.abs(distanceBound),
FLIP_DISTANCE_PER_PAGE);
}
}

private boolean drawEdgeEffects(Canvas canvas) {
return drawPreviousEdgeEffect(canvas) | drawNextEdgeEffect(canvas);
}
Expand Down Expand Up @@ -784,12 +864,16 @@ private void postFlippedToPage(final int page) {

@Override
public void run() {
mOnFlipListener.onFlippedToPage(FlipView.this, page,
mAdapter.getItemId(page));
if (mOnFlipListener != null) {
mOnFlipListener.onFlippedToPage(FlipView.this, page,
mAdapter.getItemId(page));
}
}
});
}

// binds the flip distance to the first and last page and return how much
// flipdistance was cut off. Used for calculating overflip
private float bindFlipDistance() {
final int minFlipDistance = 0;
final int maxFlipDistance = (mPageCount - 1) * FLIP_DISTANCE_PER_PAGE;
Expand Down Expand Up @@ -955,6 +1039,7 @@ public void setAdapter(ListAdapter adapter) {
if (adapter != null) {
mAdapter = adapter;
mPageCount = mAdapter.getCount();
mCurrentPageId = mAdapter.getItemId(mCurrentPage);
mAdapter.registerDataSetObserver(dataSetObserver);
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
addView(viewForPage(mCurrentPage));
Expand Down Expand Up @@ -983,6 +1068,10 @@ public void flipTo(int page) {
invalidate();
}

public void flipBy(int delta) {
flipTo(mCurrentPage + delta);
}

public void smoothFlipTo(int page) {
if (page < 0 || page > mPageCount - 1) {
throw new IllegalArgumentException("That page does not exist");
Expand All @@ -994,6 +1083,10 @@ public void smoothFlipTo(int page) {
invalidate();
}

public void smoothFlipBy(int delta) {
smoothFlipTo(mCurrentPage + delta);
}

/**
* Hint that there is a next page will do nothing if there is no next page
*
Expand Down Expand Up @@ -1030,8 +1123,42 @@ public boolean isFlippingVertically() {
return mIsFlippingVertically;
}

/**
* The OnFlipListener will notify you when a page has been fully turned.
*
* @param onFlipListener
*/
public void setOnFlipListener(OnFlipListener onFlipListener) {
mOnFlipListener = onFlipListener;
}

/**
* The OnOverFlipListener will notify of over flipping. This is a great
* listener to have when implementing pull-to-refresh
*
* @param onOverFlipListener
*/
public void setOnOverFlipListener(OnOverFlipListener onOverFlipListener) {
this.mOnOverFlipListener = onOverFlipListener;
}

/**
*
* @return the overflip mode of this flipview. Default is GLOW
*/
public OverFlipMode getOverFlipMode() {
return mOverFlipMode;
}

/**
* Set the overflip mode of the flipview. GLOW is the standard seen in all
* andriod lists. RUBBER_BAND is more like iOS lists which list you flip
* past the first/last page but adding friction, like a rubber band.
*
* @param overFlipMode
*/
public void setOverFlipMode(OverFlipMode overFlipMode) {
this.mOverFlipMode = overFlipMode;
}

}
2 changes: 1 addition & 1 deletion sample/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
android:layout_height="match_parent"
tools:context=".MainActivity"
android:id="@+id/flip_view"
flipview:orientation="horizontal"
flipview:orientation="vertical"
android:background="#ffffff">

</se.emilsjolander.flipview.FlipView>
44 changes: 33 additions & 11 deletions sample/src/se/emilsjolander/flipview/FlipAdapter.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package se.emilsjolander.flipview;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
Expand All @@ -15,12 +18,29 @@ public interface Callback{
public void onPageRequested(int page);
}

static class Item {
static long id = 0;

long mId;

public Item() {
mId = id++;
}

long getId(){
return mId;
}
}

private LayoutInflater inflater;
private Callback callback;
private int count = 10;
private List<Item> items = new ArrayList<Item>();

public FlipAdapter(Context context) {
inflater = LayoutInflater.from(context);
for(int i = 0 ; i<10 ; i++){
items.add(new Item());
}
}

public void setCallback(Callback callback) {
Expand All @@ -29,7 +49,7 @@ public void setCallback(Callback callback) {

@Override
public int getCount() {
return count ;
return items.size();
}

@Override
Expand All @@ -39,7 +59,12 @@ public Object getItem(int position) {

@Override
public long getItemId(int position) {
return position;
return items.get(position).getId();
}

@Override
public boolean hasStableIds() {
return true;
}

@Override
Expand All @@ -62,16 +87,11 @@ public View getView(int position, View convertView, ViewGroup parent) {
holder = (ViewHolder) convertView.getTag();
}

holder.text.setText(""+position);
//convertView.setBackgroundColor(getColor(position));
//TODO set a text with the id as well
holder.text.setText(items.get(position).getId()+":"+position);

return convertView;
}

private int getColor(int position) {
float t = ((float)position)/(getCount()-1);
return (int) ((1-t)*0xffff3333 + t*0xff3333ff);
}

static class ViewHolder{
TextView text;
Expand All @@ -96,7 +116,9 @@ public void onClick(View v) {
}

public void addItems(int amount) {
count += amount;
for(int i = 0 ; i<amount ; i++){
items.add(new Item());
}
notifyDataSetChanged();
}

Expand Down
2 changes: 1 addition & 1 deletion sample/src/se/emilsjolander/flipview/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ protected void onCreate(Bundle savedInstanceState) {
mAdapter.setCallback(this);
mFlipView.setAdapter(mAdapter);
mFlipView.setOnFlipListener(this);

mFlipView.peakNext(false);
mFlipView.setOverFlipMode(FlipView.OverFlipMode.RUBBER_BAND);
}

@Override
Expand Down

0 comments on commit 1b39e6e

Please sign in to comment.