package com.android.systemui.ui;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import com.android.systemui.ui.PageControl.OnPageControlClickListener;
/*
* Copyright (C) 2011 Jason Fry
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Jason Fry - jasonfry.co.uk
* @version 1.1.8
*/
public class SwipeView extends HorizontalScrollView
{
private static int DEFAULT_SWIPE_THRESHOLD = 60;
private LinearLayout mLinearLayout;
private Context mContext;
private int SCREEN_WIDTH;
private int mMotionStartX;
private int mMotionStartY;
private boolean mMostlyScrollingInX = false;
private boolean mMostlyScrollingInY = false;
private boolean mJustInterceptedAndIgnored = false;
protected boolean mCallScrollToPageInOnLayout = false;
private int mCurrentPage = 0;
private int mPageWidth = 0;
private OnPageChangedListener mOnPageChangedListener = null;
private SwipeOnTouchListener mSwipeOnTouchListener;
private View.OnTouchListener mOnTouchListener;
private PageControl mPageControl = null;
/**
* {@inheritDoc}
*/
public SwipeView(Context context)
{
super(context);
mContext = context;
initSwipeView();
}
/**
* {@inheritDoc}
*/
public SwipeView(Context context, AttributeSet attrs)
{
super(context, attrs);
mContext = context;
initSwipeView();
}
/**
* {@inheritDoc}
*/
public SwipeView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
mContext = context;
initSwipeView();
}
private void initSwipeView()
{
Log.i("uk.co.jasonfry.android.tools.ui.SwipeView", "Initialising SwipeView");
mLinearLayout = new LinearLayout(mContext);
mLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
super.addView(mLinearLayout, -1, new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
setSmoothScrollingEnabled(true);
setHorizontalFadingEdgeEnabled(false);
setHorizontalScrollBarEnabled(false);
Display display = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
SCREEN_WIDTH = (int) (display.getWidth());
mPageWidth = SCREEN_WIDTH;
mCurrentPage = 0;
mSwipeOnTouchListener = new SwipeOnTouchListener();
super.setOnTouchListener(mSwipeOnTouchListener);
}
/**
* {@inheritDoc}
*/
@Override
public boolean onTrackballEvent(MotionEvent event)
{
return true;
}
@Override
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)
{
// this will now pass trackball events down to onTrackballEvent
return false;
}
@Override
public void requestChildFocus(View child, View focused)
{
// this will now pass trackball events down to
// onRequestFocusInDescendants
requestFocus();
}
/**
* {@inheritDoc}
*/
@Override
public void addView(View child)
{
this.addView(child, -1);
}
/**
* {@inheritDoc}
*/
@Override
public void addView(View child, int index)
{
ViewGroup.LayoutParams params;
if (child.getLayoutParams() == null)
{
params = new LayoutParams(mPageWidth, LayoutParams.MATCH_PARENT);
}
else
{
params = child.getLayoutParams();
params.width = mPageWidth;
}
this.addView(child, index, params);
}
/**
* {@inheritDoc}
*/
@Override
public void addView(View child, ViewGroup.LayoutParams params)
{
params.width = mPageWidth;
this.addView(child, -1, params);
}
/**
* {@inheritDoc}
*/
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params)
{
requestLayout();
invalidate();
mLinearLayout.addView(child, index, params);
}
/**
* {@inheritDoc}
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
super.onLayout(changed, l, t, r, b);
if (mCallScrollToPageInOnLayout)
{
scrollToPage(mCurrentPage);
mCallScrollToPageInOnLayout = false;
}
}
/**
* {@inheritDoc}
*/
@Override
public void setOnTouchListener(View.OnTouchListener onTouchListener)
{
mOnTouchListener = onTouchListener;
}
/**
* Get the View object that contains all the children of this SwipeView. The
* same as calling getChildAt(0) A SwipeView behaves slightly differently
* from a normal ViewGroup, all the children of a SwipeView sit within a
* LinearLayout, which then sits within the SwipeView object.
*
* @return linearLayout The View object that contains all the children of
* this view
*/
public LinearLayout getChildContainer()
{
return mLinearLayout;
}
/**
* Get the swiping threshold distance to make the screens change
*
* @return swipeThreshold The minimum distance the finger should move to
* allow the screens to change
*/
public int getSwipeThreshold()
{
return DEFAULT_SWIPE_THRESHOLD;
}
/**
* Set the swiping threshold distance to make the screens change
*
* @param swipeThreshold The minimum distance the finger should move to
* allow the screens to change
*/
public void setSwipeThreshold(int swipeThreshold)
{
DEFAULT_SWIPE_THRESHOLD = swipeThreshold;
}
/**
* Get the current page the SwipeView is on
*
* @return The current page the SwipeView is on
*/
public int getCurrentPage()
{
return mCurrentPage;
}
/**
* Return the number of pages in this SwipeView
*
* @return Returns the number of pages in this SwipeView
*/
public int getPageCount()
{
return mLinearLayout.getChildCount();
}
/**
* Go directly to the specified page
*
* @param page The page to scroll to
*/
public void scrollToPage(int page)
{
scrollToPage(page, false);
}
/**
* Animate a scroll to the specified page
*
* @param page The page to animate to
*/
public void smoothScrollToPage(int page)
{
scrollToPage(page, true);
}
private void scrollToPage(int page, boolean smooth)
{
int oldPage = mCurrentPage;
if (page >= getPageCount() && getPageCount() > 0)
{
page--;
}
else if (page < 0)
{
page = 0;
}
if (smooth)
{
smoothScrollTo(page * mPageWidth, 0);
}
else
{
scrollTo(page * mPageWidth, 0);
}
mCurrentPage = page;
if (mOnPageChangedListener != null && oldPage != page)
{
mOnPageChangedListener.onPageChanged(oldPage, page);
}
if (mPageControl != null && oldPage != page)
{
mPageControl.setCurrentPage(page);
}
mCallScrollToPageInOnLayout = !mCallScrollToPageInOnLayout;
}
/**
* Set the width of each page. This function returns an integer that should
* be added to the left margin of the first child and the right margin of
* the last child. This enables all the children to appear to be central
*
* @param pageWidth The width you wish to assign for each page
* @return An integer to add to the left margin of the first child and the
* right margin of the last child
*/
public int setPageWidth(int pageWidth)
{
mPageWidth = pageWidth;
return (SCREEN_WIDTH - mPageWidth) / 2;
}
/**
* Set the width of each page by using the layout parameters of a child.
* Call this function before you add the child to the SwipeView to maintain
* the child's size. This function returns an integer that should be added
* to the left margin of the first child and the right margin of the last
* child. This enables all the children to appear to be central
*
* @param childLayoutParams A child view that you have added / will add to
* the SwipeView
* @return An integer to add to the left margin of the first child and the
* right margin of the last child
*/
public int calculatePageSize(MarginLayoutParams childLayoutParams)
{
return setPageWidth(childLayoutParams.leftMargin + childLayoutParams.width
+ childLayoutParams.rightMargin);
}
/**
* Return the current width of each page
*
* @return Returns the width of each page
*/
public int getPageWidth()
{
return mPageWidth;
}
/**
* Assign a PageControl object to this SwipeView. Call after adding all the
* children
*
* @param pageControl The PageControl object to assign
*/
public void setPageControl(PageControl pageControl)
{
mPageControl = pageControl;
pageControl.setPageCount(getPageCount());
pageControl.setCurrentPage(mCurrentPage);
pageControl.setOnPageControlClickListener(new OnPageControlClickListener()
{
public void goForwards()
{
smoothScrollToPage(mCurrentPage + 1);
}
public void goBackwards()
{
smoothScrollToPage(mCurrentPage - 1);
}
});
}
/**
* Return the current PageControl object
*
* @return Returns the current PageControl object
*/
public PageControl getPageControl()
{
return mPageControl;
}
/**
* Implement this listener to listen for page change events
*
* @author Jason Fry - jasonfry.co.uk
*/
public interface OnPageChangedListener
{
/**
* Event for when a page changes
*
* @param oldPage The page the view was on previously
* @param newPage The page the view has moved to
*/
public abstract void onPageChanged(int oldPage, int newPage);
}
/**
* Set the current OnPageChangedListsner
*
* @param onPageChangedListener The OnPageChangedListener object
*/
public void setOnPageChangedListener(OnPageChangedListener onPageChangedListener)
{
mOnPageChangedListener = onPageChangedListener;
}
/**
* Get the current OnPageChangeListsner
*
* @return The current OnPageChangedListener
*/
public OnPageChangedListener getOnPageChangedListener()
{
return mOnPageChangedListener;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
boolean result = super.onInterceptTouchEvent(ev);
if (ev.getAction() == MotionEvent.ACTION_DOWN)
{
mMotionStartX = (int) ev.getX();
mMotionStartY = (int) ev.getY();
if (!mJustInterceptedAndIgnored)
{
mMostlyScrollingInX = false;
mMostlyScrollingInY = false;
}
}
else if (ev.getAction() == MotionEvent.ACTION_MOVE)
{
detectMostlyScrollingDirection(ev);
}
if (mMostlyScrollingInY)
{
return false;
}
if (mMostlyScrollingInX)
{
mJustInterceptedAndIgnored = true;
return true;
}
return result;
}
private void detectMostlyScrollingDirection(MotionEvent ev)
{
if (!mMostlyScrollingInX && !mMostlyScrollingInY) // if we dont know
// which direction
// we're going yet
{
float xDistance = Math.abs(mMotionStartX - ev.getX());
float yDistance = Math.abs(mMotionStartY - ev.getY());
if (yDistance > xDistance + 5)
{
mMostlyScrollingInY = true;
}
else if (xDistance > yDistance + 5)
{
mMostlyScrollingInX = true;
}
}
}
private class SwipeOnTouchListener implements View.OnTouchListener
{
private boolean mSendingDummyMotionEvent = false;
private int mDistanceX;
private int mPreviousDirection;
private boolean mFirstMotionEvent = true;
public boolean onTouch(View v, MotionEvent event)
{
if (mOnTouchListener != null && !mJustInterceptedAndIgnored || mOnTouchListener != null
&& mSendingDummyMotionEvent) // send on touch event to
// onTouchListener set by an
// application implementing a
// SwipeView and setting their
// own onTouchListener
{
if (mOnTouchListener.onTouch(v, event))
{
if (event.getAction() == MotionEvent.ACTION_UP) // this
// comes
// back if a
// very
// quick
// movement
// event has
// happened
// over a
// view with
// an
// onClick
{
// need to call the actionUp directly so the view is not
// left between pages.
actionUp(event);
}
return true;
}
}
if (mSendingDummyMotionEvent)// if sending the fake action down
// event (to do with vertical scrolling
// within this horizontalscrollview)
// then just ignore it
{
mSendingDummyMotionEvent = false;
return false;
}
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
return actionDown(event);
case MotionEvent.ACTION_MOVE:
return actionMove(event);
case MotionEvent.ACTION_UP:
return actionUp(event);
}
return false;
}
private boolean actionDown(MotionEvent event)
{
mMotionStartX = (int) event.getX();
mMotionStartY = (int) event.getY();
mFirstMotionEvent = false;
return false;
}
private boolean actionMove(MotionEvent event)
{
int newDistance = mMotionStartX - (int) event.getX();
int newDirection;
if (newDistance < 0) // backwards
{
newDirection = (mDistanceX + 4 <= newDistance) ? 1 : -1; // the
// distance
// +4
// is
// to
// allow
// for
// jitter
}
else // forwards
{
newDirection = (mDistanceX - 4 <= newDistance) ? 1 : -1; // the
// distance
// -4
// is
// to
// allow
// for
// jitter
}
if (newDirection != mPreviousDirection && !mFirstMotionEvent)// changed
// direction,
// so
// reset
// start
// point
{
mMotionStartX = (int) event.getX();
mDistanceX = mMotionStartX - (int) event.getX();
}
else
{
mDistanceX = newDistance;
}
mPreviousDirection = newDirection; // backwards -1, forwards is 1,
if (mJustInterceptedAndIgnored)// if the intercept picked it up
// first, we need to give the
// horizontalscrollview ontouch an
// action down to enable it to scroll
// and follow your finger
{
mSendingDummyMotionEvent = true;
dispatchTouchEvent(MotionEvent.obtain(event.getDownTime(), event.getEventTime(),
MotionEvent.ACTION_DOWN, mMotionStartX, mMotionStartY, event.getPressure(),
event.getSize(), event.getMetaState(), event.getXPrecision(),
event.getYPrecision(), event.getDeviceId(), event.getEdgeFlags()));
mJustInterceptedAndIgnored = false;
return true;
}
return false;
}
private boolean actionUp(MotionEvent event)
{
float fingerUpPosition = getScrollX();
float numberOfPages = mLinearLayout.getMeasuredWidth() / mPageWidth;
float fingerUpPage = fingerUpPosition / mPageWidth;
float edgePosition = 0;
if (mPreviousDirection == 1) // forwards
{
if (mDistanceX > DEFAULT_SWIPE_THRESHOLD)// if over then go
// forwards
{
if (mCurrentPage < (numberOfPages - 1))// if not at the end
// of the pages, you
// don't want to try
// and advance into
// nothing!
{
edgePosition = (int) (fingerUpPage + 1) * mPageWidth;
}
else
{
edgePosition = (int) (mCurrentPage) * mPageWidth;
}
}
else // return to start position
{
if (Math.round(fingerUpPage) == numberOfPages - 1)// if at
// the end
{
// need to correct for when user starts to scroll into
// nothing then pulls it back a bit, this becomes a
// kind of forwards scroll instead
edgePosition = (int) (fingerUpPage + 1) * mPageWidth;
}
else // carry on as normal
{
edgePosition = mCurrentPage * mPageWidth;
}
}
}
else // backwards
{
if (mDistanceX < -DEFAULT_SWIPE_THRESHOLD)// go backwards
{
edgePosition = (int) (fingerUpPage) * mPageWidth;
}
else // return to start position
{
if (Math.round(fingerUpPage) == 0)// if at beginning,
// correct
{
// need to correct for when user starts to scroll into
// nothing then pulls it back a bit, this becomes a
// kind of backwards scroll instead
edgePosition = (int) (fingerUpPage) * mPageWidth;
}
else // carry on as normal
{
edgePosition = mCurrentPage * mPageWidth;
}
}
}
smoothScrollToPage((int) edgePosition / mPageWidth);
mFirstMotionEvent = true;
mDistanceX = 0;
mMostlyScrollingInX = false;
mMostlyScrollingInY = false;
return true;
}
}
}