Missing submodule files added

This commit is contained in:
Óscar García Amor 2015-07-26 17:21:54 +02:00
parent d4b71814cf
commit a4c38a073c
90 changed files with 11151 additions and 0 deletions

18
menudrawer/build.gradle Normal file
View File

@ -0,0 +1,18 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
minSdkVersion 7
targetSdkVersion 22
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}

91
menudrawer/menudrawer.iml Normal file
View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":menudrawer" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="UltrasSonic" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":menudrawer" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
<option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugAndroidTestSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
<option name="LIBRARY_PROJECT" value="true" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.simonvt.menudrawer"
android:versionCode="6"
android:versionName="3.0.2">
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="16" />
</manifest>

View File

@ -0,0 +1,99 @@
package net.simonvt.menudrawer;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.FrameLayout;
/**
* FrameLayout which caches the hardware layer if available.
* <p/>
* If it's not posted twice the layer either wont be built on start, or it'll be built twice.
*/
class BuildLayerFrameLayout extends FrameLayout {
private boolean mChanged;
private boolean mHardwareLayersEnabled = true;
private boolean mAttached;
private boolean mFirst = true;
public BuildLayerFrameLayout(Context context) {
super(context);
if (MenuDrawer.USE_TRANSLATIONS) {
setLayerType(LAYER_TYPE_HARDWARE, null);
}
}
public BuildLayerFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
if (MenuDrawer.USE_TRANSLATIONS) {
setLayerType(LAYER_TYPE_HARDWARE, null);
}
}
public BuildLayerFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
if (MenuDrawer.USE_TRANSLATIONS) {
setLayerType(LAYER_TYPE_HARDWARE, null);
}
}
void setHardwareLayersEnabled(boolean enabled) {
mHardwareLayersEnabled = enabled;
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mAttached = true;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mAttached = false;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (MenuDrawer.USE_TRANSLATIONS && mHardwareLayersEnabled) {
post(new Runnable() {
@Override
public void run() {
mChanged = true;
invalidate();
}
});
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mChanged && MenuDrawer.USE_TRANSLATIONS) {
post(new Runnable() {
@Override
public void run() {
if (mAttached) {
final int layerType = getLayerType();
// If it's already a hardware layer, it'll be built anyway.
if (layerType != LAYER_TYPE_HARDWARE || mFirst) {
mFirst = false;
setLayerType(LAYER_TYPE_HARDWARE, null);
buildLayer();
setLayerType(LAYER_TYPE_NONE, null);
}
}
}
});
mChanged = false;
}
}
}

View File

@ -0,0 +1,170 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* 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.
*/
package net.simonvt.menudrawer;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
/**
* A specialized Drawable that fills the Canvas with a specified color.
* Note that a ColorDrawable ignores the ColorFilter.
* <p/>
* <p>It can be defined in an XML file with the <code>&lt;color></code> element.</p>
*
* @attr ref android.R.styleable#ColorDrawable_color
*/
class ColorDrawable extends Drawable {
private ColorState mState;
private final Paint mPaint = new Paint();
/** Creates a new black ColorDrawable. */
public ColorDrawable() {
this(null);
}
/**
* Creates a new ColorDrawable with the specified color.
*
* @param color The color to draw.
*/
public ColorDrawable(int color) {
this(null);
setColor(color);
}
private ColorDrawable(ColorState state) {
mState = new ColorState(state);
}
@Override
public int getChangingConfigurations() {
return super.getChangingConfigurations() | mState.mChangingConfigurations;
}
@Override
public void draw(Canvas canvas) {
if ((mState.mUseColor >>> 24) != 0) {
mPaint.setColor(mState.mUseColor);
canvas.drawRect(getBounds(), mPaint);
}
}
/**
* Gets the drawable's color value.
*
* @return int The color to draw.
*/
public int getColor() {
return mState.mUseColor;
}
/**
* Sets the drawable's color value. This action will clobber the results of prior calls to
* {@link #setAlpha(int)} on this object, which side-affected the underlying color.
*
* @param color The color to draw.
*/
public void setColor(int color) {
if (mState.mBaseColor != color || mState.mUseColor != color) {
invalidateSelf();
mState.mBaseColor = mState.mUseColor = color;
}
}
/**
* Returns the alpha value of this drawable's color.
*
* @return A value between 0 and 255.
*/
public int getAlpha() {
return mState.mUseColor >>> 24;
}
/**
* Sets the color's alpha value.
*
* @param alpha The alpha value to set, between 0 and 255.
*/
public void setAlpha(int alpha) {
alpha += alpha >> 7; // make it 0..256
int baseAlpha = mState.mBaseColor >>> 24;
int useAlpha = baseAlpha * alpha >> 8;
int oldUseColor = mState.mUseColor;
mState.mUseColor = (mState.mBaseColor << 8 >>> 8) | (useAlpha << 24);
if (oldUseColor != mState.mUseColor) {
invalidateSelf();
}
}
/**
* Setting a color filter on a ColorDrawable has no effect.
*
* @param colorFilter Ignore.
*/
public void setColorFilter(ColorFilter colorFilter) {
}
public int getOpacity() {
switch (mState.mUseColor >>> 24) {
case 255:
return PixelFormat.OPAQUE;
case 0:
return PixelFormat.TRANSPARENT;
}
return PixelFormat.TRANSLUCENT;
}
@Override
public ConstantState getConstantState() {
mState.mChangingConfigurations = getChangingConfigurations();
return mState;
}
static final class ColorState extends ConstantState {
int mBaseColor; // base color, independent of setAlpha()
int mUseColor; // basecolor modulated by setAlpha()
int mChangingConfigurations;
ColorState(ColorState state) {
if (state != null) {
mBaseColor = state.mBaseColor;
mUseColor = state.mUseColor;
}
}
@Override
public Drawable newDrawable() {
return new ColorDrawable(this);
}
@Override
public Drawable newDrawable(Resources res) {
return new ColorDrawable(this);
}
@Override
public int getChangingConfigurations() {
return mChangingConfigurations;
}
}
}

View File

@ -0,0 +1,619 @@
package net.simonvt.menudrawer;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
public abstract class DraggableDrawer extends MenuDrawer {
/**
* Key used when saving menu visibility state.
*/
private static final String STATE_MENU_VISIBLE = "net.simonvt.menudrawer.MenuDrawer.menuVisible";
/**
* Interpolator used for peeking at the drawer.
*/
private static final Interpolator PEEK_INTERPOLATOR = new PeekInterpolator();
/**
* The maximum alpha of the dark menu overlay used for dimming the menu.
*/
protected static final int MAX_MENU_OVERLAY_ALPHA = 185;
/**
* Default delay from {@link #peekDrawer()} is called until first animation is run.
*/
private static final long DEFAULT_PEEK_START_DELAY = 5000;
/**
* Default delay between each subsequent animation, after {@link #peekDrawer()} has been called.
*/
private static final long DEFAULT_PEEK_DELAY = 10000;
/**
* The duration of the peek animation.
*/
protected static final int PEEK_DURATION = 5000;
/**
* Distance in dp from closed position from where the drawer is considered closed with regards to touch events.
*/
private static final int CLOSE_ENOUGH = 3;
protected static final int INVALID_POINTER = -1;
/**
* Slop before starting a drag.
*/
protected int mTouchSlop;
/**
* Runnable used when the peek animation is running.
*/
protected final Runnable mPeekRunnable = new Runnable() {
@Override
public void run() {
peekDrawerInvalidate();
}
};
/**
* Runnable used when animating the drawer open/closed.
*/
private final Runnable mDragRunnable = new Runnable() {
@Override
public void run() {
postAnimationInvalidate();
}
};
/**
* Indicates whether the drawer is currently being dragged.
*/
protected boolean mIsDragging;
/**
* The current pointer id.
*/
protected int mActivePointerId = INVALID_POINTER;
/**
* The initial X position of a drag.
*/
protected float mInitialMotionX;
/**
* The initial Y position of a drag.
*/
protected float mInitialMotionY;
/**
* The last X position of a drag.
*/
protected float mLastMotionX = -1;
/**
* The last Y position of a drag.
*/
protected float mLastMotionY = -1;
/**
* Default delay between each subsequent animation, after {@link #peekDrawer()} has been called.
*/
protected long mPeekDelay;
/**
* Scroller used for the peek drawer animation.
*/
protected Scroller mPeekScroller;
/**
* Velocity tracker used when animating the drawer open/closed after a drag.
*/
protected VelocityTracker mVelocityTracker;
/**
* Maximum velocity allowed when animating the drawer open/closed.
*/
protected int mMaxVelocity;
/**
* Indicates whether the menu should be offset when dragging the drawer.
*/
protected boolean mOffsetMenu = true;
/**
* Distance in px from closed position from where the drawer is considered closed with regards to touch events.
*/
protected int mCloseEnough;
/**
* Runnable used for first call to {@link #startPeek()} after {@link #peekDrawer()} has been called.
*/
private Runnable mPeekStartRunnable;
/**
* Scroller used when animating the drawer open/closed.
*/
private Scroller mScroller;
/**
* Indicates whether the current layer type is {@link android.view.View#LAYER_TYPE_HARDWARE}.
*/
protected boolean mLayerTypeHardware;
DraggableDrawer(Activity activity, int dragMode) {
super(activity, dragMode);
}
public DraggableDrawer(Context context) {
super(context);
}
public DraggableDrawer(Context context, AttributeSet attrs) {
super(context, attrs);
}
public DraggableDrawer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
super.initDrawer(context, attrs, defStyle);
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
mMaxVelocity = configuration.getScaledMaximumFlingVelocity();
mScroller = new Scroller(context, MenuDrawer.SMOOTH_INTERPOLATOR);
mPeekScroller = new Scroller(context, DraggableDrawer.PEEK_INTERPOLATOR);
mCloseEnough = dpToPx(DraggableDrawer.CLOSE_ENOUGH);
}
public void toggleMenu(boolean animate) {
if (mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING) {
closeMenu(animate);
} else if (mDrawerState == STATE_CLOSED || mDrawerState == STATE_CLOSING) {
openMenu(animate);
}
}
public boolean isMenuVisible() {
return mMenuVisible;
}
public void setMenuSize(final int size) {
mMenuSize = size;
if (mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING) {
setOffsetPixels(mMenuSize);
}
requestLayout();
invalidate();
}
public void setOffsetMenuEnabled(boolean offsetMenu) {
if (offsetMenu != mOffsetMenu) {
mOffsetMenu = offsetMenu;
requestLayout();
invalidate();
}
}
public boolean getOffsetMenuEnabled() {
return mOffsetMenu;
}
public void peekDrawer() {
peekDrawer(DEFAULT_PEEK_START_DELAY, DEFAULT_PEEK_DELAY);
}
public void peekDrawer(long delay) {
peekDrawer(DEFAULT_PEEK_START_DELAY, delay);
}
public void peekDrawer(final long startDelay, final long delay) {
if (startDelay < 0) {
throw new IllegalArgumentException("startDelay must be zero or larger.");
}
if (delay < 0) {
throw new IllegalArgumentException("delay must be zero or larger");
}
removeCallbacks(mPeekRunnable);
removeCallbacks(mPeekStartRunnable);
mPeekDelay = delay;
mPeekStartRunnable = new Runnable() {
@Override
public void run() {
startPeek();
}
};
postDelayed(mPeekStartRunnable, startDelay);
}
public void setHardwareLayerEnabled(boolean enabled) {
if (enabled != mHardwareLayersEnabled) {
mHardwareLayersEnabled = enabled;
mMenuContainer.setHardwareLayersEnabled(enabled);
mContentContainer.setHardwareLayersEnabled(enabled);
stopLayerTranslation();
}
}
public int getTouchMode() {
return mTouchMode;
}
public void setTouchMode(int mode) {
if (mTouchMode != mode) {
mTouchMode = mode;
updateTouchAreaSize();
}
}
public void setTouchBezelSize(int size) {
mTouchBezelSize = size;
}
public int getTouchBezelSize() {
return mTouchBezelSize;
}
/**
* If possible, set the layer type to {@link android.view.View#LAYER_TYPE_HARDWARE}.
*/
protected void startLayerTranslation() {
if (USE_TRANSLATIONS && mHardwareLayersEnabled && !mLayerTypeHardware) {
mLayerTypeHardware = true;
mContentContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
mMenuContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
}
/**
* If the current layer type is {@link android.view.View#LAYER_TYPE_HARDWARE}, this will set it to
* {@link View#LAYER_TYPE_NONE}.
*/
protected void stopLayerTranslation() {
if (mLayerTypeHardware) {
mLayerTypeHardware = false;
mContentContainer.setLayerType(View.LAYER_TYPE_NONE, null);
mMenuContainer.setLayerType(View.LAYER_TYPE_NONE, null);
}
}
/**
* Called when a drag has been ended.
*/
protected void endDrag() {
mIsDragging = false;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
/**
* Stops ongoing animation of the drawer.
*/
protected void stopAnimation() {
removeCallbacks(mDragRunnable);
mScroller.abortAnimation();
stopLayerTranslation();
}
/**
* Called when a drawer animation has successfully completed.
*/
private void completeAnimation() {
mScroller.abortAnimation();
final int finalX = mScroller.getFinalX();
setOffsetPixels(finalX);
setDrawerState(finalX == 0 ? STATE_CLOSED : STATE_OPEN);
stopLayerTranslation();
}
protected void cancelContentTouch() {
final long now = SystemClock.uptimeMillis();
final MotionEvent cancelEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
getChildAt(i).dispatchTouchEvent(cancelEvent);
}
mContentContainer.dispatchTouchEvent(cancelEvent);
cancelEvent.recycle();
}
/**
* Moves the drawer to the position passed.
*
* @param position The position the content is moved to.
* @param velocity Optional velocity if called by releasing a drag event.
* @param animate Whether the move is animated.
*/
protected void animateOffsetTo(int position, int velocity, boolean animate) {
endDrag();
endPeek();
final int startX = (int) mOffsetPixels;
final int dx = position - startX;
if (dx == 0 || !animate) {
setOffsetPixels(position);
setDrawerState(position == 0 ? STATE_CLOSED : STATE_OPEN);
stopLayerTranslation();
return;
}
int duration;
velocity = Math.abs(velocity);
if (velocity > 0) {
duration = 4 * Math.round(1000.f * Math.abs((float) dx / velocity));
} else {
duration = (int) (600.f * Math.abs((float) dx / mMenuSize));
}
duration = Math.min(duration, mMaxAnimationDuration);
animateOffsetTo(position, duration);
}
protected void animateOffsetTo(int position, int duration) {
final int startX = (int) mOffsetPixels;
final int dx = position - startX;
if (dx > 0) {
setDrawerState(STATE_OPENING);
mScroller.startScroll(startX, 0, dx, 0, duration);
} else {
setDrawerState(STATE_CLOSING);
mScroller.startScroll(startX, 0, dx, 0, duration);
}
startLayerTranslation();
postAnimationInvalidate();
}
/**
* Callback when each frame in the drawer animation should be drawn.
*/
private void postAnimationInvalidate() {
if (mScroller.computeScrollOffset()) {
final int oldX = (int) mOffsetPixels;
final int x = mScroller.getCurrX();
if (x != oldX) setOffsetPixels(x);
if (x != mScroller.getFinalX()) {
postOnAnimation(mDragRunnable);
return;
}
}
completeAnimation();
}
/**
* Starts peek drawer animation.
*/
protected void startPeek() {
initPeekScroller();
startLayerTranslation();
peekDrawerInvalidate();
}
protected abstract void initPeekScroller();
/**
* Callback when each frame in the peek drawer animation should be drawn.
*/
private void peekDrawerInvalidate() {
if (mPeekScroller.computeScrollOffset()) {
final int oldX = (int) mOffsetPixels;
final int x = mPeekScroller.getCurrX();
if (x != oldX) setOffsetPixels(x);
if (!mPeekScroller.isFinished()) {
postOnAnimation(mPeekRunnable);
return;
} else if (mPeekDelay > 0) {
mPeekStartRunnable = new Runnable() {
@Override
public void run() {
startPeek();
}
};
postDelayed(mPeekStartRunnable, mPeekDelay);
}
}
completePeek();
}
/**
* Called when the peek drawer animation has successfully completed.
*/
private void completePeek() {
mPeekScroller.abortAnimation();
setOffsetPixels(0);
setDrawerState(STATE_CLOSED);
stopLayerTranslation();
}
/**
* Stops ongoing peek drawer animation.
*/
protected void endPeek() {
removeCallbacks(mPeekStartRunnable);
removeCallbacks(mPeekRunnable);
stopLayerTranslation();
}
protected boolean isCloseEnough() {
return Math.abs(mOffsetPixels) <= mCloseEnough;
}
protected boolean canChildrenScroll(int dx, int dy, int x, int y) {
boolean canScroll = false;
switch (getPosition()) {
case LEFT:
case RIGHT:
if (!mMenuVisible) {
canScroll = canChildScrollHorizontally(mContentContainer, false, dx,
x - ViewHelper.getLeft(mContentContainer), y - ViewHelper.getTop(mContentContainer));
} else {
canScroll = canChildScrollHorizontally(mMenuContainer, false, dx,
x - ViewHelper.getLeft(mMenuContainer), y - ViewHelper.getTop(mContentContainer));
}
break;
case TOP:
case BOTTOM:
if (!mMenuVisible) {
canScroll = canChildScrollVertically(mContentContainer, false, dy,
x - ViewHelper.getLeft(mContentContainer), y - ViewHelper.getTop(mContentContainer));
} else {
canScroll = canChildScrollVertically(mMenuContainer, false, dy,
x - ViewHelper.getLeft(mMenuContainer), y - ViewHelper.getTop(mContentContainer));
}
}
return canScroll;
}
/**
* Tests scrollability within child views of v given a delta of dx.
*
* @param v View to test for horizontal scrollability
* @param checkV Whether the view should be checked for draggability
* @param dx Delta scrolled in pixels
* @param x X coordinate of the active touch point
* @param y Y coordinate of the active touch point
* @return true if child views of v can be scrolled by delta of dx.
*/
protected boolean canChildScrollHorizontally(View v, boolean checkV, int dx, int x, int y) {
if (v instanceof ViewGroup) {
final ViewGroup group = (ViewGroup) v;
final int count = group.getChildCount();
// Count backwards - let topmost views consume scroll distance first.
for (int i = count - 1; i >= 0; i--) {
final View child = group.getChildAt(i);
final int childLeft = child.getLeft() + supportGetTranslationX(child);
final int childRight = child.getRight() + supportGetTranslationX(child);
final int childTop = child.getTop() + supportGetTranslationY(child);
final int childBottom = child.getBottom() + supportGetTranslationY(child);
if (x >= childLeft && x < childRight && y >= childTop && y < childBottom
&& canChildScrollHorizontally(child, true, dx, x - childLeft, y - childTop)) {
return true;
}
}
}
return checkV && mOnInterceptMoveEventListener.isViewDraggable(v, dx, x, y);
}
/**
* Tests scrollability within child views of v given a delta of dx.
*
* @param v View to test for horizontal scrollability
* @param checkV Whether the view should be checked for draggability
* @param dx Delta scrolled in pixels
* @param x X coordinate of the active touch point
* @param y Y coordinate of the active touch point
* @return true if child views of v can be scrolled by delta of dx.
*/
protected boolean canChildScrollVertically(View v, boolean checkV, int dx, int x, int y) {
if (v instanceof ViewGroup) {
final ViewGroup group = (ViewGroup) v;
final int count = group.getChildCount();
// Count backwards - let topmost views consume scroll distance first.
for (int i = count - 1; i >= 0; i--) {
final View child = group.getChildAt(i);
final int childLeft = child.getLeft() + supportGetTranslationX(child);
final int childRight = child.getRight() + supportGetTranslationX(child);
final int childTop = child.getTop() + supportGetTranslationY(child);
final int childBottom = child.getBottom() + supportGetTranslationY(child);
if (x >= childLeft && x < childRight && y >= childTop && y < childBottom
&& canChildScrollVertically(child, true, dx, x - childLeft, y - childTop)) {
return true;
}
}
}
return checkV && mOnInterceptMoveEventListener.isViewDraggable(v, dx, x, y);
}
protected float getXVelocity(VelocityTracker velocityTracker) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
return velocityTracker.getXVelocity(mActivePointerId);
}
return velocityTracker.getXVelocity();
}
protected float getYVelocity(VelocityTracker velocityTracker) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
return velocityTracker.getYVelocity(mActivePointerId);
}
return velocityTracker.getYVelocity();
}
private int supportGetTranslationY(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return (int) v.getTranslationY();
}
return 0;
}
private int supportGetTranslationX(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return (int) v.getTranslationX();
}
return 0;
}
void saveState(Bundle state) {
final boolean menuVisible = mDrawerState == STATE_OPEN || mDrawerState == STATE_OPENING;
state.putBoolean(STATE_MENU_VISIBLE, menuVisible);
}
public void restoreState(Parcelable in) {
super.restoreState(in);
Bundle state = (Bundle) in;
final boolean menuOpen = state.getBoolean(STATE_MENU_VISIBLE);
if (menuOpen) {
openMenu(false);
} else {
setOffsetPixels(0);
}
mDrawerState = menuOpen ? STATE_OPEN : STATE_CLOSED;
}
}

View File

@ -0,0 +1,175 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* 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.
*/
package net.simonvt.menudrawer;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
/**
* This class encapsulates scrolling. The duration of the scroll
* can be passed in the constructor and specifies the maximum time that
* the scrolling animation should take. Past this time, the scrolling is
* automatically moved to its final stage and computeScrollOffset()
* will always return false to indicate that scrolling is over.
*/
class FloatScroller {
private float mStart;
private float mFinal;
private float mCurr;
private long mStartTime;
private int mDuration;
private float mDurationReciprocal;
private float mDeltaX;
private boolean mFinished;
private Interpolator mInterpolator;
/**
* Create a Scroller with the specified interpolator. If the interpolator is
* null, the default (viscous) interpolator will be used. Specify whether or
* not to support progressive "flywheel" behavior in flinging.
*/
public FloatScroller(Interpolator interpolator) {
mFinished = true;
mInterpolator = interpolator;
}
/**
* Returns whether the scroller has finished scrolling.
*
* @return True if the scroller has finished scrolling, false otherwise.
*/
public final boolean isFinished() {
return mFinished;
}
/**
* Force the finished field to a particular value.
*
* @param finished The new finished value.
*/
public final void forceFinished(boolean finished) {
mFinished = finished;
}
/**
* Returns how long the scroll event will take, in milliseconds.
*
* @return The duration of the scroll in milliseconds.
*/
public final int getDuration() {
return mDuration;
}
/**
* Returns the current offset in the scroll.
*
* @return The new offset as an absolute distance from the origin.
*/
public final float getCurr() {
return mCurr;
}
/**
* Returns the start offset in the scroll.
*
* @return The start offset as an absolute distance from the origin.
*/
public final float getStart() {
return mStart;
}
/**
* Returns where the scroll will end. Valid only for "fling" scrolls.
*
* @return The final offset as an absolute distance from the origin.
*/
public final float getFinal() {
return mFinal;
}
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
float x = timePassed * mDurationReciprocal;
x = mInterpolator.getInterpolation(x);
mCurr = mStart + x * mDeltaX;
} else {
mCurr = mFinal;
mFinished = true;
}
return true;
}
public void startScroll(float start, float delta, int duration) {
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStart = start;
mFinal = start + delta;
mDeltaX = delta;
mDurationReciprocal = 1.0f / (float) mDuration;
}
/**
* Stops the animation. Contrary to {@link #forceFinished(boolean)},
* aborting the animating cause the scroller to move to the final x and y
* position
*
* @see #forceFinished(boolean)
*/
public void abortAnimation() {
mCurr = mFinal;
mFinished = true;
}
/**
* Extend the scroll animation. This allows a running animation to scroll
* further and longer, when used with {@link #setFinal(float)}.
*
* @param extend Additional time to scroll in milliseconds.
* @see #setFinal(float)
*/
public void extendDuration(int extend) {
int passed = timePassed();
mDuration = passed + extend;
mDurationReciprocal = 1.0f / mDuration;
mFinished = false;
}
/**
* Returns the time elapsed since the beginning of the scrolling.
*
* @return The elapsed time in milliseconds.
*/
public int timePassed() {
return (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
}
public void setFinal(float newVal) {
mFinal = newVal;
mDeltaX = mFinal - mStart;
mFinished = false;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
package net.simonvt.menudrawer;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
/**
* FrameLayout which doesn't let touch events propagate to views positioned behind it in the view hierarchy.
*/
class NoClickThroughFrameLayout extends BuildLayerFrameLayout {
public NoClickThroughFrameLayout(Context context) {
super(context);
}
public NoClickThroughFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NoClickThroughFrameLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return true;
}
}

View File

@ -0,0 +1,781 @@
package net.simonvt.menudrawer;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
public class OverlayDrawer extends DraggableDrawer {
private static final String TAG = "OverlayDrawer";
private int mPeekSize;
private Runnable mRevealRunnable = new Runnable() {
@Override
public void run() {
cancelContentTouch();
int animateTo = 0;
switch (getPosition()) {
case RIGHT:
case BOTTOM:
animateTo = -mPeekSize;
break;
default:
animateTo = mPeekSize;
break;
}
animateOffsetTo(animateTo, 250);
}
};
OverlayDrawer(Activity activity, int dragMode) {
super(activity, dragMode);
}
public OverlayDrawer(Context context) {
super(context);
}
public OverlayDrawer(Context context, AttributeSet attrs) {
super(context, attrs);
}
public OverlayDrawer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
super.initDrawer(context, attrs, defStyle);
super.addView(mContentContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
if (USE_TRANSLATIONS) {
mContentContainer.setLayerType(View.LAYER_TYPE_NONE, null);
}
mContentContainer.setHardwareLayersEnabled(false);
super.addView(mMenuContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mPeekSize = dpToPx(20);
}
@Override
protected void drawOverlay(Canvas canvas) {
final int width = getWidth();
final int height = getHeight();
final int offsetPixels = (int) mOffsetPixels;
final float openRatio = Math.abs(mOffsetPixels) / mMenuSize;
switch (getPosition()) {
case LEFT:
mMenuOverlay.setBounds(offsetPixels, 0, width, height);
break;
case RIGHT:
mMenuOverlay.setBounds(0, 0, width + offsetPixels, height);
break;
case TOP:
mMenuOverlay.setBounds(0, offsetPixels, width, height);
break;
case BOTTOM:
mMenuOverlay.setBounds(0, 0, width, height + offsetPixels);
break;
}
mMenuOverlay.setAlpha((int) (MAX_MENU_OVERLAY_ALPHA * openRatio));
mMenuOverlay.draw(canvas);
}
@Override
public void openMenu(boolean animate) {
int animateTo = 0;
switch (getPosition()) {
case LEFT:
case TOP:
animateTo = mMenuSize;
break;
case RIGHT:
case BOTTOM:
animateTo = -mMenuSize;
break;
}
animateOffsetTo(animateTo, 0, animate);
}
@Override
public void closeMenu(boolean animate) {
animateOffsetTo(0, 0, animate);
}
@Override
protected void onOffsetPixelsChanged(int offsetPixels) {
if (USE_TRANSLATIONS) {
switch (getPosition()) {
case LEFT:
mMenuContainer.setTranslationX(offsetPixels - mMenuSize);
break;
case TOP:
mMenuContainer.setTranslationY(offsetPixels - mMenuSize);
break;
case RIGHT:
mMenuContainer.setTranslationX(offsetPixels + mMenuSize);
break;
case BOTTOM:
mMenuContainer.setTranslationY(offsetPixels + mMenuSize);
break;
}
} else {
switch (getPosition()) {
case TOP:
mMenuContainer.offsetTopAndBottom(offsetPixels - mMenuContainer.getBottom());
break;
case BOTTOM:
mMenuContainer.offsetTopAndBottom(offsetPixels - (mMenuContainer.getTop() - getHeight()));
break;
case LEFT:
mMenuContainer.offsetLeftAndRight(offsetPixels - mMenuContainer.getRight());
break;
case RIGHT:
mMenuContainer.offsetLeftAndRight(offsetPixels - (mMenuContainer.getLeft() - getWidth()));
break;
}
}
invalidate();
}
@Override
protected void initPeekScroller() {
switch (getPosition()) {
case RIGHT:
case BOTTOM: {
final int dx = -mPeekSize;
mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
break;
}
default: {
final int dx = mPeekSize;
mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
break;
}
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
onOffsetPixelsChanged((int) mOffsetPixels);
}
@Override
protected GradientDrawable.Orientation getDropShadowOrientation() {
switch (getPosition()) {
case TOP:
return GradientDrawable.Orientation.TOP_BOTTOM;
case RIGHT:
return GradientDrawable.Orientation.RIGHT_LEFT;
case BOTTOM:
return GradientDrawable.Orientation.BOTTOM_TOP;
default:
return GradientDrawable.Orientation.LEFT_RIGHT;
}
}
@Override
protected void updateDropShadowRect() {
final float openRatio = Math.abs(mOffsetPixels) / mMenuSize;
final int dropShadowSize = (int) (mDropShadowSize * openRatio);
switch (getPosition()) {
case LEFT:
mDropShadowRect.top = 0;
mDropShadowRect.bottom = getHeight();
mDropShadowRect.left = ViewHelper.getRight(mMenuContainer);
mDropShadowRect.right = mDropShadowRect.left + dropShadowSize;
break;
case TOP:
mDropShadowRect.left = 0;
mDropShadowRect.right = getWidth();
mDropShadowRect.top = ViewHelper.getBottom(mMenuContainer);
mDropShadowRect.bottom = mDropShadowRect.top + dropShadowSize;
break;
case RIGHT:
mDropShadowRect.top = 0;
mDropShadowRect.bottom = getHeight();
mDropShadowRect.right = ViewHelper.getLeft(mMenuContainer);
mDropShadowRect.left = mDropShadowRect.right - dropShadowSize;
break;
case BOTTOM:
mDropShadowRect.left = 0;
mDropShadowRect.right = getWidth();
mDropShadowRect.bottom = ViewHelper.getTop(mMenuContainer);
mDropShadowRect.top = mDropShadowRect.bottom - dropShadowSize;
break;
}
}
@Override
protected void startLayerTranslation() {
if (USE_TRANSLATIONS && mHardwareLayersEnabled && !mLayerTypeHardware) {
mLayerTypeHardware = true;
mMenuContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
}
@Override
protected void stopLayerTranslation() {
if (mLayerTypeHardware) {
mLayerTypeHardware = false;
mMenuContainer.setLayerType(View.LAYER_TYPE_NONE, null);
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = r - l;
final int height = b - t;
mContentContainer.layout(0, 0, width, height);
if (USE_TRANSLATIONS) {
switch (getPosition()) {
case LEFT:
mMenuContainer.layout(0, 0, mMenuSize, height);
break;
case RIGHT:
mMenuContainer.layout(width - mMenuSize, 0, width, height);
break;
case TOP:
mMenuContainer.layout(0, 0, width, mMenuSize);
break;
case BOTTOM:
mMenuContainer.layout(0, height - mMenuSize, width, height);
break;
}
} else {
final int offsetPixels = (int) mOffsetPixels;
final int menuSize = mMenuSize;
switch (getPosition()) {
case LEFT:
mMenuContainer.layout(-menuSize + offsetPixels, 0, offsetPixels, height);
break;
case RIGHT:
mMenuContainer.layout(width + offsetPixels, 0, width + menuSize + offsetPixels, height);
break;
case TOP:
mMenuContainer.layout(0, -menuSize + offsetPixels, width, offsetPixels);
break;
case BOTTOM:
mMenuContainer.layout(0, height + offsetPixels, width, height + menuSize + offsetPixels);
break;
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
throw new IllegalStateException("Must measure with an exact size");
}
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int height = MeasureSpec.getSize(heightMeasureSpec);
if (mOffsetPixels == -1) openMenu(false);
int menuWidthMeasureSpec;
int menuHeightMeasureSpec;
switch (getPosition()) {
case TOP:
case BOTTOM:
menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
menuHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, mMenuSize);
break;
default:
// LEFT/RIGHT
menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, mMenuSize);
menuHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
}
mMenuContainer.measure(menuWidthMeasureSpec, menuHeightMeasureSpec);
final int contentWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
final int contentHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
mContentContainer.measure(contentWidthMeasureSpec, contentHeightMeasureSpec);
setMeasuredDimension(width, height);
updateTouchAreaSize();
}
private boolean isContentTouch(int x, int y) {
boolean contentTouch = false;
switch (getPosition()) {
case LEFT:
contentTouch = ViewHelper.getRight(mMenuContainer) < x;
break;
case RIGHT:
contentTouch = ViewHelper.getLeft(mMenuContainer) > x;
break;
case TOP:
contentTouch = ViewHelper.getBottom(mMenuContainer) < y;
break;
case BOTTOM:
contentTouch = ViewHelper.getTop(mMenuContainer) > y;
break;
}
return contentTouch;
}
protected boolean onDownAllowDrag(int x, int y) {
switch (getPosition()) {
case LEFT:
return (!mMenuVisible && mInitialMotionX <= mTouchSize)
|| (mMenuVisible && mInitialMotionX <= mOffsetPixels);
case RIGHT:
final int width = getWidth();
final int initialMotionX = (int) mInitialMotionX;
return (!mMenuVisible && initialMotionX >= width - mTouchSize)
|| (mMenuVisible && initialMotionX >= width + mOffsetPixels);
case TOP:
return (!mMenuVisible && mInitialMotionY <= mTouchSize)
|| (mMenuVisible && mInitialMotionY <= mOffsetPixels);
case BOTTOM:
final int height = getHeight();
return (!mMenuVisible && mInitialMotionY >= height - mTouchSize)
|| (mMenuVisible && mInitialMotionY >= height + mOffsetPixels);
}
return false;
}
protected boolean onMoveAllowDrag(int x, int y, float dx, float dy) {
if (mMenuVisible && mTouchMode == TOUCH_MODE_FULLSCREEN) {
return true;
}
switch (getPosition()) {
case LEFT:
return (!mMenuVisible && mInitialMotionX <= mTouchSize && (dx > 0)) // Drawer closed
|| (mMenuVisible && x <= mOffsetPixels) // Drawer open
|| (Math.abs(mOffsetPixels) <= mPeekSize && mMenuVisible); // Drawer revealed
case RIGHT:
final int width = getWidth();
return (!mMenuVisible && mInitialMotionX >= width - mTouchSize && (dx < 0))
|| (mMenuVisible && x >= width - mOffsetPixels)
|| (Math.abs(mOffsetPixels) <= mPeekSize && mMenuVisible);
case TOP:
return (!mMenuVisible && mInitialMotionY <= mTouchSize && (dy > 0))
|| (mMenuVisible && x <= mOffsetPixels)
|| (Math.abs(mOffsetPixels) <= mPeekSize && mMenuVisible);
case BOTTOM:
final int height = getHeight();
return (!mMenuVisible && mInitialMotionY >= height - mTouchSize && (dy < 0))
|| (mMenuVisible && x >= height - mOffsetPixels)
|| (Math.abs(mOffsetPixels) <= mPeekSize && mMenuVisible);
}
return false;
}
protected void onMoveEvent(float dx, float dy) {
switch (getPosition()) {
case LEFT:
setOffsetPixels(Math.min(Math.max(mOffsetPixels + dx, 0), mMenuSize));
break;
case RIGHT:
setOffsetPixels(Math.max(Math.min(mOffsetPixels + dx, 0), -mMenuSize));
break;
case TOP:
setOffsetPixels(Math.min(Math.max(mOffsetPixels + dy, 0), mMenuSize));
break;
case BOTTOM:
setOffsetPixels(Math.max(Math.min(mOffsetPixels + dy, 0), -mMenuSize));
break;
}
}
protected void onUpEvent(int x, int y) {
final int offsetPixels = (int) mOffsetPixels;
switch (getPosition()) {
case LEFT: {
if (mIsDragging) {
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
final int initialVelocity = (int) getXVelocity(mVelocityTracker);
mLastMotionX = x;
animateOffsetTo(initialVelocity > 0 ? mMenuSize : 0, initialVelocity, true);
// Close the menu when content is clicked while the menu is visible.
} else if (mMenuVisible) {
closeMenu();
}
break;
}
case TOP: {
if (mIsDragging) {
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
final int initialVelocity = (int) getYVelocity(mVelocityTracker);
mLastMotionY = y;
animateOffsetTo(initialVelocity > 0 ? mMenuSize : 0, initialVelocity, true);
// Close the menu when content is clicked while the menu is visible.
} else if (mMenuVisible) {
closeMenu();
}
break;
}
case RIGHT: {
final int width = getWidth();
if (mIsDragging) {
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
final int initialVelocity = (int) getXVelocity(mVelocityTracker);
mLastMotionX = x;
animateOffsetTo(initialVelocity > 0 ? 0 : -mMenuSize, initialVelocity, true);
// Close the menu when content is clicked while the menu is visible.
} else if (mMenuVisible) {
closeMenu();
}
break;
}
case BOTTOM: {
if (mIsDragging) {
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
final int initialVelocity = (int) getYVelocity(mVelocityTracker);
mLastMotionY = y;
animateOffsetTo(initialVelocity < 0 ? -mMenuSize : 0, initialVelocity, true);
// Close the menu when content is clicked while the menu is visible.
} else if (mMenuVisible) {
closeMenu();
}
break;
}
}
}
protected boolean checkTouchSlop(float dx, float dy) {
switch (getPosition()) {
case TOP:
case BOTTOM:
return Math.abs(dy) > mTouchSlop && Math.abs(dy) > Math.abs(dx);
default:
return Math.abs(dx) > mTouchSlop && Math.abs(dx) > Math.abs(dy);
}
}
@Override
protected void stopAnimation() {
super.stopAnimation();
removeCallbacks(mRevealRunnable);
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
removeCallbacks(mRevealRunnable);
mActivePointerId = INVALID_POINTER;
mIsDragging = false;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
if (Math.abs(mOffsetPixels) > mMenuSize / 2) {
openMenu();
} else {
closeMenu();
}
return false;
}
if (action == MotionEvent.ACTION_DOWN && mMenuVisible && isCloseEnough()) {
setOffsetPixels(0);
stopAnimation();
endPeek();
setDrawerState(STATE_CLOSED);
mIsDragging = false;
}
// Always intercept events over the content while menu is visible.
if (mMenuVisible) {
int index = 0;
if (mActivePointerId != INVALID_POINTER) {
index = ev.findPointerIndex(mActivePointerId);
index = index == -1 ? 0 : index;
}
final int x = (int) ev.getX(index);
final int y = (int) ev.getY(index);
if (isContentTouch(x, y)) {
return true;
}
}
if (!mMenuVisible && !mIsDragging && mTouchMode == TOUCH_MODE_NONE) {
return false;
}
if (action != MotionEvent.ACTION_DOWN && mIsDragging) {
return true;
}
switch (action) {
case MotionEvent.ACTION_DOWN: {
mLastMotionX = mInitialMotionX = ev.getX();
mLastMotionY = mInitialMotionY = ev.getY();
final boolean allowDrag = onDownAllowDrag((int) mLastMotionX, (int) mLastMotionY);
mActivePointerId = ev.getPointerId(0);
if (allowDrag) {
setDrawerState(mMenuVisible ? STATE_OPEN : STATE_CLOSED);
stopAnimation();
endPeek();
if (!mMenuVisible && mInitialMotionX <= mPeekSize) {
postDelayed(mRevealRunnable, 160);
}
mIsDragging = false;
}
break;
}
case MotionEvent.ACTION_MOVE: {
final int activePointerId = mActivePointerId;
if (activePointerId == INVALID_POINTER) {
// If we don't have a valid id, the touch down wasn't on content.
break;
}
final int pointerIndex = ev.findPointerIndex(activePointerId);
if (pointerIndex == -1) {
mIsDragging = false;
mActivePointerId = INVALID_POINTER;
endDrag();
closeMenu(true);
return false;
}
final float x = ev.getX(pointerIndex);
final float dx = x - mLastMotionX;
final float y = ev.getY(pointerIndex);
final float dy = y - mLastMotionY;
if (Math.abs(dx) >= mTouchSlop || Math.abs(dy) >= mTouchSlop) {
removeCallbacks(mRevealRunnable);
endPeek();
}
if (checkTouchSlop(dx, dy)) {
if (mOnInterceptMoveEventListener != null && (mTouchMode == TOUCH_MODE_FULLSCREEN || mMenuVisible)
&& canChildrenScroll((int) dx, (int) dy, (int) x, (int) y)) {
endDrag(); // Release the velocity tracker
requestDisallowInterceptTouchEvent(true);
return false;
}
final boolean allowDrag = onMoveAllowDrag((int) x, (int) y, dx, dy);
if (allowDrag) {
endPeek();
stopAnimation();
setDrawerState(STATE_DRAGGING);
mIsDragging = true;
mLastMotionX = x;
mLastMotionY = y;
}
}
break;
}
case MotionEvent.ACTION_POINTER_UP:
onPointerUp(ev);
mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId));
break;
}
if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(ev);
return mIsDragging;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!mMenuVisible && !mIsDragging && mTouchMode == TOUCH_MODE_NONE) {
return false;
}
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(ev);
switch (action) {
case MotionEvent.ACTION_DOWN: {
mLastMotionX = mInitialMotionX = ev.getX();
mLastMotionY = mInitialMotionY = ev.getY();
final boolean allowDrag = onDownAllowDrag((int) mLastMotionX, (int) mLastMotionY);
mActivePointerId = ev.getPointerId(0);
if (allowDrag) {
stopAnimation();
endPeek();
if (!mMenuVisible && mLastMotionX <= mPeekSize) {
postDelayed(mRevealRunnable, 160);
}
startLayerTranslation();
}
break;
}
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
mIsDragging = false;
mActivePointerId = INVALID_POINTER;
endDrag();
closeMenu(true);
return false;
}
if (!mIsDragging) {
final float x = ev.getX(pointerIndex);
final float dx = x - mLastMotionX;
final float y = ev.getY(pointerIndex);
final float dy = y - mLastMotionY;
if (checkTouchSlop(dx, dy)) {
final boolean allowDrag = onMoveAllowDrag((int) x, (int) y, dx, dy);
if (allowDrag) {
endPeek();
stopAnimation();
setDrawerState(STATE_DRAGGING);
mIsDragging = true;
mLastMotionX = x;
mLastMotionY = y;
} else {
mInitialMotionX = x;
mInitialMotionY = y;
}
}
}
if (mIsDragging) {
startLayerTranslation();
final float x = ev.getX(pointerIndex);
final float dx = x - mLastMotionX;
final float y = ev.getY(pointerIndex);
final float dy = y - mLastMotionY;
mLastMotionX = x;
mLastMotionY = y;
onMoveEvent(dx, dy);
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
removeCallbacks(mRevealRunnable);
int index = ev.findPointerIndex(mActivePointerId);
index = index == -1 ? 0 : index;
final int x = (int) ev.getX(index);
final int y = (int) ev.getY(index);
onUpEvent(x, y);
mActivePointerId = INVALID_POINTER;
mIsDragging = false;
break;
}
case MotionEvent.ACTION_POINTER_DOWN:
final int index = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
mLastMotionX = ev.getX(index);
mLastMotionY = ev.getY(index);
mActivePointerId = ev.getPointerId(index);
break;
case MotionEvent.ACTION_POINTER_UP:
onPointerUp(ev);
mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId));
break;
}
return true;
}
private void onPointerUp(MotionEvent ev) {
final int pointerIndex = ev.getActionIndex();
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastMotionX = ev.getX(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
}
}
}

View File

@ -0,0 +1,28 @@
package net.simonvt.menudrawer;
import android.view.animation.Interpolator;
class PeekInterpolator implements Interpolator {
private static final String TAG = "PeekInterpolator";
private static final SinusoidalInterpolator SINUSOIDAL_INTERPOLATOR = new SinusoidalInterpolator();
@Override
public float getInterpolation(float input) {
float result;
if (input < 1.f / 3.f) {
result = SINUSOIDAL_INTERPOLATOR.getInterpolation(input * 3);
} else if (input > 2.f / 3.f) {
final float val = ((input + 1.f / 3.f) - 1.f) * 3.f;
result = 1.f - SINUSOIDAL_INTERPOLATOR.getInterpolation(val);
} else {
result = 1.f;
}
return result;
}
}

View File

@ -0,0 +1,50 @@
package net.simonvt.menudrawer;
import android.util.SparseArray;
/**
* Enums used for positioning the drawer.
*/
public enum Position {
// Positions the drawer to the left of the content.
LEFT(0),
// Positions the drawer above the content.
TOP(1),
// Positions the drawer to the right of the content.
RIGHT(2),
// Positions the drawer below the content.
BOTTOM(3),
/**
* Position the drawer at the start edge. This will position the drawer to the {@link #LEFT} with LTR languages and
* {@link #RIGHT} with RTL languages.
*/
START(4),
/**
* Position the drawer at the end edge. This will position the drawer to the {@link #RIGHT} with LTR languages and
* {@link #LEFT} with RTL languages.
*/
END(5);
final int mValue;
Position(int value) {
mValue = value;
}
private static final SparseArray<Position> STRING_MAPPING = new SparseArray<Position>();
static {
for (Position via : Position.values()) {
STRING_MAPPING.put(via.mValue, via);
}
}
public static Position fromValue(int value) {
return STRING_MAPPING.get(value);
}
}

View File

@ -0,0 +1,505 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* 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.
*/
package net.simonvt.menudrawer;
import android.content.Context;
import android.hardware.SensorManager;
import android.os.Build;
import android.util.FloatMath;
import android.view.ViewConfiguration;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
/**
* This class encapsulates scrolling. The duration of the scroll
* can be passed in the constructor and specifies the maximum time that
* the scrolling animation should take. Past this time, the scrolling is
* automatically moved to its final stage and computeScrollOffset()
* will always return false to indicate that scrolling is over.
*/
class Scroller {
private int mMode;
private int mStartX;
private int mStartY;
private int mFinalX;
private int mFinalY;
private int mMinX;
private int mMaxX;
private int mMinY;
private int mMaxY;
private int mCurrX;
private int mCurrY;
private long mStartTime;
private int mDuration;
private float mDurationReciprocal;
private float mDeltaX;
private float mDeltaY;
private boolean mFinished;
private Interpolator mInterpolator;
private boolean mFlywheel;
private float mVelocity;
private static final int DEFAULT_DURATION = 250;
private static final int SCROLL_MODE = 0;
private static final int FLING_MODE = 1;
private static final float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
private static final float ALPHA = 800; // pixels / seconds
private static final float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance)
private static final float END_TENSION = 1.0f - START_TENSION;
private static final int NB_SAMPLES = 100;
private static final float[] SPLINE = new float[NB_SAMPLES + 1];
private float mDeceleration;
private final float mPpi;
static {
float xMin = 0.0f;
for (int i = 0; i <= NB_SAMPLES; i++) {
final float t = (float) i / NB_SAMPLES;
float xMax = 1.0f;
float x, tx, coef;
while (true) {
x = xMin + (xMax - xMin) / 2.0f;
coef = 3.0f * x * (1.0f - x);
tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x;
if (Math.abs(tx - t) < 1E-5) break;
if (tx > t) xMax = x;
else xMin = x;
}
final float d = coef + x * x * x;
SPLINE[i] = d;
}
SPLINE[NB_SAMPLES] = 1.0f;
// This controls the viscous fluid effect (how much of it)
sViscousFluidScale = 8.0f;
// must be set to 1.0 (used in viscousFluid())
sViscousFluidNormalize = 1.0f;
sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
}
private static float sViscousFluidScale;
private static float sViscousFluidNormalize;
/**
* Create a Scroller with the default duration and interpolator.
*/
public Scroller(Context context) {
this(context, null);
}
/**
* Create a Scroller with the specified interpolator. If the interpolator is
* null, the default (viscous) interpolator will be used. "Flywheel" behavior will
* be in effect for apps targeting Honeycomb or newer.
*/
public Scroller(Context context, Interpolator interpolator) {
this(context, interpolator,
context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
}
/**
* Create a Scroller with the specified interpolator. If the interpolator is
* null, the default (viscous) interpolator will be used. Specify whether or
* not to support progressive "flywheel" behavior in flinging.
*/
public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
mFinished = true;
mInterpolator = interpolator;
mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
mFlywheel = flywheel;
}
/**
* The amount of friction applied to flings. The default value
* is {@link android.view.ViewConfiguration#getScrollFriction}.
*
* @param friction A scalar dimension-less value representing the coefficient of
* friction.
*/
public final void setFriction(float friction) {
mDeceleration = computeDeceleration(friction);
}
private float computeDeceleration(float friction) {
return SensorManager.GRAVITY_EARTH // g (m/s^2)
* 39.37f // inch/meter
* mPpi // pixels per inch
* friction;
}
/**
*
* Returns whether the scroller has finished scrolling.
*
* @return True if the scroller has finished scrolling, false otherwise.
*/
public final boolean isFinished() {
return mFinished;
}
/**
* Force the finished field to a particular value.
*
* @param finished The new finished value.
*/
public final void forceFinished(boolean finished) {
mFinished = finished;
}
/**
* Returns how long the scroll event will take, in milliseconds.
*
* @return The duration of the scroll in milliseconds.
*/
public final int getDuration() {
return mDuration;
}
/**
* Returns the current X offset in the scroll.
*
* @return The new X offset as an absolute distance from the origin.
*/
public final int getCurrX() {
return mCurrX;
}
/**
* Returns the current Y offset in the scroll.
*
* @return The new Y offset as an absolute distance from the origin.
*/
public final int getCurrY() {
return mCurrY;
}
/**
* Returns the current velocity.
*
* @return The original velocity less the deceleration. Result may be
* negative.
*/
public float getCurrVelocity() {
return mVelocity - mDeceleration * timePassed() / 2000.0f;
}
/**
* Returns the start X offset in the scroll.
*
* @return The start X offset as an absolute distance from the origin.
*/
public final int getStartX() {
return mStartX;
}
/**
* Returns the start Y offset in the scroll.
*
* @return The start Y offset as an absolute distance from the origin.
*/
public final int getStartY() {
return mStartY;
}
/**
* Returns where the scroll will end. Valid only for "fling" scrolls.
*
* @return The final X offset as an absolute distance from the origin.
*/
public final int getFinalX() {
return mFinalX;
}
/**
* Returns where the scroll will end. Valid only for "fling" scrolls.
*
* @return The final Y offset as an absolute distance from the origin.
*/
public final int getFinalY() {
return mFinalY;
}
/**
* Call this when you want to know the new location. If it returns true,
* the animation is not yet finished. loc will be altered to provide the
* new location.
*/
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
int timePassed = (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
float x = timePassed * mDurationReciprocal;
if (mInterpolator == null)
x = viscousFluid(x);
else
x = mInterpolator.getInterpolation(x);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
case FLING_MODE:
final float t = (float) timePassed / mDuration;
final int index = (int) (NB_SAMPLES * t);
final float tInf = (float) index / NB_SAMPLES;
final float tSup = (float) (index + 1) / NB_SAMPLES;
final float dInf = SPLINE[index];
final float dSup = SPLINE[index + 1];
final float distanceCoef = dInf + (t - tInf) / (tSup - tInf) * (dSup - dInf);
mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
// Pin to mMinX <= mCurrX <= mMaxX
mCurrX = Math.min(mCurrX, mMaxX);
mCurrX = Math.max(mCurrX, mMinX);
mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
// Pin to mMinY <= mCurrY <= mMaxY
mCurrY = Math.min(mCurrY, mMaxY);
mCurrY = Math.max(mCurrY, mMinY);
if (mCurrX == mFinalX && mCurrY == mFinalY) {
mFinished = true;
}
break;
}
} else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
/**
* Start scrolling by providing a starting point and the distance to travel.
* The scroll will use the default value of 250 milliseconds for the
* duration.
*
* @param startX Starting horizontal scroll offset in pixels. Positive
* numbers will scroll the content to the left.
* @param startY Starting vertical scroll offset in pixels. Positive numbers
* will scroll the content up.
* @param dx Horizontal distance to travel. Positive numbers will scroll the
* content to the left.
* @param dy Vertical distance to travel. Positive numbers will scroll the
* content up.
*/
public void startScroll(int startX, int startY, int dx, int dy) {
startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
}
/**
* Start scrolling by providing a starting point and the distance to travel.
*
* @param startX Starting horizontal scroll offset in pixels. Positive
* numbers will scroll the content to the left.
* @param startY Starting vertical scroll offset in pixels. Positive numbers
* will scroll the content up.
* @param dx Horizontal distance to travel. Positive numbers will scroll the
* content to the left.
* @param dy Vertical distance to travel. Positive numbers will scroll the
* content up.
* @param duration Duration of the scroll in milliseconds.
*/
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
}
/**
* Start scrolling based on a fling gesture. The distance travelled will
* depend on the initial velocity of the fling.
*
* @param startX Starting point of the scroll (X)
* @param startY Starting point of the scroll (Y)
* @param velocityX Initial velocity of the fling (X) measured in pixels per
* second.
* @param velocityY Initial velocity of the fling (Y) measured in pixels per
* second
* @param minX Minimum X value. The scroller will not scroll past this
* point.
* @param maxX Maximum X value. The scroller will not scroll past this
* point.
* @param minY Minimum Y value. The scroller will not scroll past this
* point.
* @param maxY Maximum Y value. The scroller will not scroll past this
* point.
*/
public void fling(int startX, int startY, int velocityX, int velocityY,
int minX, int maxX, int minY, int maxY) {
// Continue a scroll or fling in progress
if (mFlywheel && !mFinished) {
float oldVel = getCurrVelocity();
float dx = (float) (mFinalX - mStartX);
float dy = (float) (mFinalY - mStartY);
float hyp = FloatMath.sqrt(dx * dx + dy * dy);
float ndx = dx / hyp;
float ndy = dy / hyp;
float oldVelocityX = ndx * oldVel;
float oldVelocityY = ndy * oldVel;
if (Math.signum(velocityX) == Math.signum(oldVelocityX)
&& Math.signum(velocityY) == Math.signum(oldVelocityY)) {
velocityX += oldVelocityX;
velocityY += oldVelocityY;
}
}
mMode = FLING_MODE;
mFinished = false;
float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);
mVelocity = velocity;
final double l = Math.log(START_TENSION * velocity / ALPHA);
mDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
int totalDistance =
(int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
mMinX = minX;
mMaxX = maxX;
mMinY = minY;
mMaxY = maxY;
mFinalX = startX + Math.round(totalDistance * coeffX);
// Pin to mMinX <= mFinalX <= mMaxX
mFinalX = Math.min(mFinalX, mMaxX);
mFinalX = Math.max(mFinalX, mMinX);
mFinalY = startY + Math.round(totalDistance * coeffY);
// Pin to mMinY <= mFinalY <= mMaxY
mFinalY = Math.min(mFinalY, mMaxY);
mFinalY = Math.max(mFinalY, mMinY);
}
static float viscousFluid(float x) {
x *= sViscousFluidScale;
if (x < 1.0f) {
x -= (1.0f - (float) Math.exp(-x));
} else {
float start = 0.36787944117f; // 1/e == exp(-1)
x = 1.0f - (float) Math.exp(1.0f - x);
x = start + x * (1.0f - start);
}
x *= sViscousFluidNormalize;
return x;
}
/**
* Stops the animation. Contrary to {@link #forceFinished(boolean)},
* aborting the animating cause the scroller to move to the final x and y
* position
*
* @see #forceFinished(boolean)
*/
public void abortAnimation() {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
/**
* Extend the scroll animation. This allows a running animation to scroll
* further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
*
* @param extend Additional time to scroll in milliseconds.
* @see #setFinalX(int)
* @see #setFinalY(int)
*/
public void extendDuration(int extend) {
int passed = timePassed();
mDuration = passed + extend;
mDurationReciprocal = 1.0f / mDuration;
mFinished = false;
}
/**
* Returns the time elapsed since the beginning of the scrolling.
*
* @return The elapsed time in milliseconds.
*/
public int timePassed() {
return (int) (AnimationUtils.currentAnimationTimeMillis() - mStartTime);
}
/**
* Sets the final position (X) for this scroller.
*
* @param newX The new X offset as an absolute distance from the origin.
* @see #extendDuration(int)
* @see #setFinalY(int)
*/
public void setFinalX(int newX) {
mFinalX = newX;
mDeltaX = mFinalX - mStartX;
mFinished = false;
}
/**
* Sets the final position (Y) for this scroller.
*
* @param newY The new Y offset as an absolute distance from the origin.
* @see #extendDuration(int)
* @see #setFinalX(int)
*/
public void setFinalY(int newY) {
mFinalY = newY;
mDeltaY = mFinalY - mStartY;
mFinished = false;
}
/**
* @hide
*/
public boolean isScrollingInDirection(float xvel, float yvel) {
return !mFinished && Math.signum(xvel) == Math.signum(mFinalX - mStartX)
&& Math.signum(yvel) == Math.signum(mFinalY - mStartY);
}
}

View File

@ -0,0 +1,15 @@
package net.simonvt.menudrawer;
import android.view.animation.Interpolator;
/**
* Interpolator which, when drawn from 0 to 1, looks like half a sine-wave. Used for smoother opening/closing when
* peeking at the drawer.
*/
class SinusoidalInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
return (float) (0.5f + 0.5f * Math.sin(input * Math.PI - Math.PI / 2.f));
}
}

View File

@ -0,0 +1,187 @@
package net.simonvt.menudrawer;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
public class SlideDrawable extends Drawable implements Drawable.Callback {
private Drawable mWrapped;
private float mOffset;
private final Rect mTmpRect = new Rect();
private boolean mIsRtl;
public SlideDrawable(Drawable wrapped) {
mWrapped = wrapped;
}
public void setOffset(float offset) {
mOffset = offset;
invalidateSelf();
}
public float getOffset() {
return mOffset;
}
void setIsRtl(boolean isRtl) {
mIsRtl = isRtl;
invalidateSelf();
}
@Override
public void draw(Canvas canvas) {
mWrapped.copyBounds(mTmpRect);
canvas.save();
if (mIsRtl) {
canvas.translate(1.f / 3 * mTmpRect.width() * mOffset, 0);
} else {
canvas.translate(1.f / 3 * mTmpRect.width() * -mOffset, 0);
}
mWrapped.draw(canvas);
canvas.restore();
}
@Override
public void setChangingConfigurations(int configs) {
mWrapped.setChangingConfigurations(configs);
}
@Override
public int getChangingConfigurations() {
return mWrapped.getChangingConfigurations();
}
@Override
public void setDither(boolean dither) {
mWrapped.setDither(dither);
}
@Override
public void setFilterBitmap(boolean filter) {
mWrapped.setFilterBitmap(filter);
}
@Override
public void setAlpha(int alpha) {
mWrapped.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
mWrapped.setColorFilter(cf);
}
@Override
public void setColorFilter(int color, PorterDuff.Mode mode) {
mWrapped.setColorFilter(color, mode);
}
@Override
public void clearColorFilter() {
mWrapped.clearColorFilter();
}
@Override
public boolean isStateful() {
return mWrapped.isStateful();
}
@Override
public boolean setState(int[] stateSet) {
return mWrapped.setState(stateSet);
}
@Override
public int[] getState() {
return mWrapped.getState();
}
@Override
public Drawable getCurrent() {
return mWrapped.getCurrent();
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
return super.setVisible(visible, restart);
}
@Override
public int getOpacity() {
return mWrapped.getOpacity();
}
@Override
public Region getTransparentRegion() {
return mWrapped.getTransparentRegion();
}
@Override
protected boolean onStateChange(int[] state) {
mWrapped.setState(state);
return super.onStateChange(state);
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mWrapped.setBounds(bounds);
}
@Override
public int getIntrinsicWidth() {
return mWrapped.getIntrinsicWidth();
}
@Override
public int getIntrinsicHeight() {
return mWrapped.getIntrinsicHeight();
}
@Override
public int getMinimumWidth() {
return mWrapped.getMinimumWidth();
}
@Override
public int getMinimumHeight() {
return mWrapped.getMinimumHeight();
}
@Override
public boolean getPadding(Rect padding) {
return mWrapped.getPadding(padding);
}
@Override
public ConstantState getConstantState() {
return super.getConstantState();
}
@Override
public void invalidateDrawable(Drawable who) {
if (who == mWrapped) {
invalidateSelf();
}
}
@Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
if (who == mWrapped) {
scheduleSelf(what, when);
}
}
@Override
public void unscheduleDrawable(Drawable who, Runnable what) {
if (who == mWrapped) {
unscheduleSelf(what);
}
}
}

View File

@ -0,0 +1,707 @@
package net.simonvt.menudrawer;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
public class SlidingDrawer extends DraggableDrawer {
private static final String TAG = "OverlayDrawer";
SlidingDrawer(Activity activity, int dragMode) {
super(activity, dragMode);
}
public SlidingDrawer(Context context) {
super(context);
}
public SlidingDrawer(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SlidingDrawer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
super.initDrawer(context, attrs, defStyle);
super.addView(mMenuContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
super.addView(mContentContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
@Override
public void openMenu(boolean animate) {
int animateTo = 0;
switch (getPosition()) {
case LEFT:
case TOP:
animateTo = mMenuSize;
break;
case RIGHT:
case BOTTOM:
animateTo = -mMenuSize;
break;
}
animateOffsetTo(animateTo, 0, animate);
}
@Override
public void closeMenu(boolean animate) {
animateOffsetTo(0, 0, animate);
}
@Override
protected void onOffsetPixelsChanged(int offsetPixels) {
if (USE_TRANSLATIONS) {
switch (getPosition()) {
case TOP:
case BOTTOM:
mContentContainer.setTranslationY(offsetPixels);
break;
default:
mContentContainer.setTranslationX(offsetPixels);
break;
}
} else {
switch (getPosition()) {
case TOP:
case BOTTOM:
mContentContainer.offsetTopAndBottom(offsetPixels - mContentContainer.getTop());
break;
default:
mContentContainer.offsetLeftAndRight(offsetPixels - mContentContainer.getLeft());
break;
}
}
offsetMenu(offsetPixels);
invalidate();
}
@Override
protected void initPeekScroller() {
switch (getPosition()) {
case RIGHT:
case BOTTOM: {
final int dx = -mMenuSize / 3;
mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
break;
}
default: {
final int dx = mMenuSize / 3;
mPeekScroller.startScroll(0, 0, dx, 0, PEEK_DURATION);
break;
}
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
onOffsetPixelsChanged((int) mOffsetPixels);
}
@Override
protected void drawOverlay(Canvas canvas) {
final int width = getWidth();
final int height = getHeight();
final int offsetPixels = (int) mOffsetPixels;
final float openRatio = Math.abs(mOffsetPixels) / mMenuSize;
switch (getPosition()) {
case LEFT:
mMenuOverlay.setBounds(0, 0, offsetPixels, height);
break;
case RIGHT:
mMenuOverlay.setBounds(width + offsetPixels, 0, width, height);
break;
case TOP:
mMenuOverlay.setBounds(0, 0, width, offsetPixels);
break;
case BOTTOM:
mMenuOverlay.setBounds(0, height + offsetPixels, width, height);
break;
}
mMenuOverlay.setAlpha((int) (MAX_MENU_OVERLAY_ALPHA * (1.f - openRatio)));
mMenuOverlay.draw(canvas);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = r - l;
final int height = b - t;
if (USE_TRANSLATIONS) {
mContentContainer.layout(0, 0, width, height);
} else {
final int offsetPixels = (int) mOffsetPixels;
if (getPosition() == Position.LEFT || getPosition() == Position.RIGHT) {
mContentContainer.layout(offsetPixels, 0, width + offsetPixels, height);
} else {
mContentContainer.layout(0, offsetPixels, width, height + offsetPixels);
}
}
switch (getPosition()) {
case LEFT:
mMenuContainer.layout(0, 0, mMenuSize, height);
break;
case RIGHT:
mMenuContainer.layout(width - mMenuSize, 0, width, height);
break;
case TOP:
mMenuContainer.layout(0, 0, width, mMenuSize);
break;
case BOTTOM:
mMenuContainer.layout(0, height - mMenuSize, width, height);
break;
}
}
/**
* Offsets the menu relative to its original position based on the position of the content.
*
* @param offsetPixels The number of pixels the content if offset.
*/
private void offsetMenu(int offsetPixels) {
if (!mOffsetMenu || mMenuSize == 0) {
return;
}
final int width = getWidth();
final int height = getHeight();
final int menuSize = mMenuSize;
final int sign = (int) (mOffsetPixels / Math.abs(mOffsetPixels));
final float openRatio = Math.abs(mOffsetPixels) / menuSize;
final int offset = (int) (-0.25f * ((1.0f - openRatio) * menuSize) * sign);
switch (getPosition()) {
case LEFT: {
if (USE_TRANSLATIONS) {
if (offsetPixels > 0) {
mMenuContainer.setTranslationX(offset);
} else {
mMenuContainer.setTranslationX(-menuSize);
}
} else {
mMenuContainer.offsetLeftAndRight(offset - mMenuContainer.getLeft());
mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
}
break;
}
case RIGHT: {
if (USE_TRANSLATIONS) {
if (offsetPixels != 0) {
mMenuContainer.setTranslationX(offset);
} else {
mMenuContainer.setTranslationX(menuSize);
}
} else {
final int oldOffset = mMenuContainer.getRight() - width;
final int offsetBy = offset - oldOffset;
mMenuContainer.offsetLeftAndRight(offsetBy);
mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
}
break;
}
case TOP: {
if (USE_TRANSLATIONS) {
if (offsetPixels > 0) {
mMenuContainer.setTranslationY(offset);
} else {
mMenuContainer.setTranslationY(-menuSize);
}
} else {
mMenuContainer.offsetTopAndBottom(offset - mMenuContainer.getTop());
mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
}
break;
}
case BOTTOM: {
if (USE_TRANSLATIONS) {
if (offsetPixels != 0) {
mMenuContainer.setTranslationY(offset);
} else {
mMenuContainer.setTranslationY(menuSize);
}
} else {
final int oldOffset = mMenuContainer.getBottom() - height;
final int offsetBy = offset - oldOffset;
mMenuContainer.offsetTopAndBottom(offsetBy);
mMenuContainer.setVisibility(offsetPixels == 0 ? INVISIBLE : VISIBLE);
}
break;
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
throw new IllegalStateException("Must measure with an exact size");
}
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int height = MeasureSpec.getSize(heightMeasureSpec);
if (mOffsetPixels == -1) openMenu(false);
int menuWidthMeasureSpec;
int menuHeightMeasureSpec;
switch (getPosition()) {
case TOP:
case BOTTOM:
menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
menuHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, mMenuSize);
break;
default:
// LEFT/RIGHT
menuWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, mMenuSize);
menuHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
}
mMenuContainer.measure(menuWidthMeasureSpec, menuHeightMeasureSpec);
final int contentWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, width);
final int contentHeightMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, height);
mContentContainer.measure(contentWidthMeasureSpec, contentHeightMeasureSpec);
setMeasuredDimension(width, height);
updateTouchAreaSize();
}
private boolean isContentTouch(int x, int y) {
boolean contentTouch = false;
switch (getPosition()) {
case LEFT:
contentTouch = ViewHelper.getLeft(mContentContainer) < x;
break;
case RIGHT:
contentTouch = ViewHelper.getRight(mContentContainer) > x;
break;
case TOP:
contentTouch = ViewHelper.getTop(mContentContainer) < y;
break;
case BOTTOM:
contentTouch = ViewHelper.getBottom(mContentContainer) > y;
break;
}
return contentTouch;
}
protected boolean onDownAllowDrag(int x, int y) {
switch (getPosition()) {
case LEFT:
return (!mMenuVisible && mInitialMotionX <= mTouchSize)
|| (mMenuVisible && mInitialMotionX >= mOffsetPixels);
case RIGHT:
final int width = getWidth();
final int initialMotionX = (int) mInitialMotionX;
return (!mMenuVisible && initialMotionX >= width - mTouchSize)
|| (mMenuVisible && initialMotionX <= width + mOffsetPixels);
case TOP:
return (!mMenuVisible && mInitialMotionY <= mTouchSize)
|| (mMenuVisible && mInitialMotionY >= mOffsetPixels);
case BOTTOM:
final int height = getHeight();
return (!mMenuVisible && mInitialMotionY >= height - mTouchSize)
|| (mMenuVisible && mInitialMotionY <= height + mOffsetPixels);
}
return false;
}
protected boolean onMoveAllowDrag(int x, int y, float dx, float dy) {
switch (getPosition()) {
case LEFT:
return (!mMenuVisible && mInitialMotionX <= mTouchSize && (dx > 0))
|| (mMenuVisible && x >= mOffsetPixels);
case RIGHT:
final int width = getWidth();
return (!mMenuVisible && mInitialMotionX >= width - mTouchSize && (dx < 0))
|| (mMenuVisible && x <= width + mOffsetPixels);
case TOP:
return (!mMenuVisible && mInitialMotionY <= mTouchSize && (dy > 0))
|| (mMenuVisible && y >= mOffsetPixels);
case BOTTOM:
final int height = getHeight();
return (!mMenuVisible && mInitialMotionY >= height - mTouchSize && (dy < 0))
|| (mMenuVisible && y <= height + mOffsetPixels);
}
return false;
}
protected void onMoveEvent(float dx, float dy) {
switch (getPosition()) {
case LEFT:
setOffsetPixels(Math.min(Math.max(mOffsetPixels + dx, 0), mMenuSize));
break;
case RIGHT:
setOffsetPixels(Math.max(Math.min(mOffsetPixels + dx, 0), -mMenuSize));
break;
case TOP:
setOffsetPixels(Math.min(Math.max(mOffsetPixels + dy, 0), mMenuSize));
break;
case BOTTOM:
setOffsetPixels(Math.max(Math.min(mOffsetPixels + dy, 0), -mMenuSize));
break;
}
}
protected void onUpEvent(int x, int y) {
final int offsetPixels = (int) mOffsetPixels;
switch (getPosition()) {
case LEFT: {
if (mIsDragging) {
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
final int initialVelocity = (int) getXVelocity(mVelocityTracker);
mLastMotionX = x;
animateOffsetTo(initialVelocity > 0 ? mMenuSize : 0, initialVelocity, true);
// Close the menu when content is clicked while the menu is visible.
} else if (mMenuVisible && x > offsetPixels) {
closeMenu();
}
break;
}
case TOP: {
if (mIsDragging) {
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
final int initialVelocity = (int) getYVelocity(mVelocityTracker);
mLastMotionY = y;
animateOffsetTo(initialVelocity > 0 ? mMenuSize : 0, initialVelocity, true);
// Close the menu when content is clicked while the menu is visible.
} else if (mMenuVisible && y > offsetPixels) {
closeMenu();
}
break;
}
case RIGHT: {
final int width = getWidth();
if (mIsDragging) {
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
final int initialVelocity = (int) getXVelocity(mVelocityTracker);
mLastMotionX = x;
animateOffsetTo(initialVelocity > 0 ? 0 : -mMenuSize, initialVelocity, true);
// Close the menu when content is clicked while the menu is visible.
} else if (mMenuVisible && x < width + offsetPixels) {
closeMenu();
}
break;
}
case BOTTOM: {
if (mIsDragging) {
mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity);
final int initialVelocity = (int) getYVelocity(mVelocityTracker);
mLastMotionY = y;
animateOffsetTo(initialVelocity < 0 ? -mMenuSize : 0, initialVelocity, true);
// Close the menu when content is clicked while the menu is visible.
} else if (mMenuVisible && y < getHeight() + offsetPixels) {
closeMenu();
}
break;
}
}
}
protected boolean checkTouchSlop(float dx, float dy) {
switch (getPosition()) {
case TOP:
case BOTTOM:
return Math.abs(dy) > mTouchSlop && Math.abs(dy) > Math.abs(dx);
default:
return Math.abs(dx) > mTouchSlop && Math.abs(dx) > Math.abs(dy);
}
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
mActivePointerId = INVALID_POINTER;
mIsDragging = false;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
if (Math.abs(mOffsetPixels) > mMenuSize / 2) {
openMenu();
} else {
closeMenu();
}
return false;
}
if (action == MotionEvent.ACTION_DOWN && mMenuVisible && isCloseEnough()) {
setOffsetPixels(0);
stopAnimation();
endPeek();
setDrawerState(STATE_CLOSED);
mIsDragging = false;
}
// Always intercept events over the content while menu is visible.
if (mMenuVisible) {
int index = 0;
if (mActivePointerId != INVALID_POINTER) {
index = ev.findPointerIndex(mActivePointerId);
index = index == -1 ? 0 : index;
}
final int x = (int) ev.getX(index);
final int y = (int) ev.getY(index);
if (isContentTouch(x, y)) {
return true;
}
}
if (!mMenuVisible && !mIsDragging && mTouchMode == TOUCH_MODE_NONE) {
return false;
}
if (action != MotionEvent.ACTION_DOWN && mIsDragging) {
return true;
}
switch (action) {
case MotionEvent.ACTION_DOWN: {
mLastMotionX = mInitialMotionX = ev.getX();
mLastMotionY = mInitialMotionY = ev.getY();
final boolean allowDrag = onDownAllowDrag((int) mLastMotionX, (int) mLastMotionY);
mActivePointerId = ev.getPointerId(0);
if (allowDrag) {
setDrawerState(mMenuVisible ? STATE_OPEN : STATE_CLOSED);
stopAnimation();
endPeek();
mIsDragging = false;
}
break;
}
case MotionEvent.ACTION_MOVE: {
final int activePointerId = mActivePointerId;
if (activePointerId == INVALID_POINTER) {
// If we don't have a valid id, the touch down wasn't on content.
break;
}
final int pointerIndex = ev.findPointerIndex(activePointerId);
if (pointerIndex == -1) {
mIsDragging = false;
mActivePointerId = INVALID_POINTER;
endDrag();
closeMenu(true);
return false;
}
final float x = ev.getX(pointerIndex);
final float dx = x - mLastMotionX;
final float y = ev.getY(pointerIndex);
final float dy = y - mLastMotionY;
if (checkTouchSlop(dx, dy)) {
if (mOnInterceptMoveEventListener != null && (mTouchMode == TOUCH_MODE_FULLSCREEN || mMenuVisible)
&& canChildrenScroll((int) dx, (int) dy, (int) x, (int) y)) {
endDrag(); // Release the velocity tracker
requestDisallowInterceptTouchEvent(true);
return false;
}
final boolean allowDrag = onMoveAllowDrag((int) x, (int) y, dx, dy);
if (allowDrag) {
setDrawerState(STATE_DRAGGING);
mIsDragging = true;
mLastMotionX = x;
mLastMotionY = y;
}
}
break;
}
case MotionEvent.ACTION_POINTER_UP:
onPointerUp(ev);
mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId));
break;
}
if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(ev);
return mIsDragging;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (!mMenuVisible && !mIsDragging && mTouchMode == TOUCH_MODE_NONE) {
return false;
}
final int action = ev.getAction() & MotionEvent.ACTION_MASK;
if (mVelocityTracker == null) mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(ev);
switch (action) {
case MotionEvent.ACTION_DOWN: {
mLastMotionX = mInitialMotionX = ev.getX();
mLastMotionY = mInitialMotionY = ev.getY();
final boolean allowDrag = onDownAllowDrag((int) mLastMotionX, (int) mLastMotionY);
mActivePointerId = ev.getPointerId(0);
if (allowDrag) {
stopAnimation();
endPeek();
startLayerTranslation();
}
break;
}
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
mIsDragging = false;
mActivePointerId = INVALID_POINTER;
endDrag();
closeMenu(true);
return false;
}
if (!mIsDragging) {
final float x = ev.getX(pointerIndex);
final float dx = x - mLastMotionX;
final float y = ev.getY(pointerIndex);
final float dy = y - mLastMotionY;
if (checkTouchSlop(dx, dy)) {
final boolean allowDrag = onMoveAllowDrag((int) x, (int) y, dx, dy);
if (allowDrag) {
setDrawerState(STATE_DRAGGING);
mIsDragging = true;
mLastMotionX = x;
mLastMotionY = y;
} else {
mInitialMotionX = x;
mInitialMotionY = y;
}
}
}
if (mIsDragging) {
startLayerTranslation();
final float x = ev.getX(pointerIndex);
final float dx = x - mLastMotionX;
final float y = ev.getY(pointerIndex);
final float dy = y - mLastMotionY;
mLastMotionX = x;
mLastMotionY = y;
onMoveEvent(dx, dy);
}
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
int index = ev.findPointerIndex(mActivePointerId);
index = index == -1 ? 0 : index;
final int x = (int) ev.getX(index);
final int y = (int) ev.getY(index);
onUpEvent(x, y);
mActivePointerId = INVALID_POINTER;
mIsDragging = false;
break;
}
case MotionEvent.ACTION_POINTER_DOWN:
final int index = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
mLastMotionX = ev.getX(index);
mLastMotionY = ev.getY(index);
mActivePointerId = ev.getPointerId(index);
break;
case MotionEvent.ACTION_POINTER_UP:
onPointerUp(ev);
mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
mLastMotionY = ev.getY(ev.findPointerIndex(mActivePointerId));
break;
}
return true;
}
private void onPointerUp(MotionEvent ev) {
final int pointerIndex = ev.getActionIndex();
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastMotionX = ev.getX(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
}
}
}

View File

@ -0,0 +1,12 @@
package net.simonvt.menudrawer;
import android.view.animation.Interpolator;
class SmoothInterpolator implements Interpolator {
@Override
public float getInterpolation(float t) {
t -= 1.0f;
return t * t * t * t * t + 1.0f;
}
}

View File

@ -0,0 +1,218 @@
package net.simonvt.menudrawer;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
public class StaticDrawer extends MenuDrawer {
public StaticDrawer(Context context) {
super(context);
}
public StaticDrawer(Context context, AttributeSet attrs) {
super(context, attrs);
}
public StaticDrawer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void initDrawer(Context context, AttributeSet attrs, int defStyle) {
super.initDrawer(context, attrs, defStyle);
super.addView(mMenuContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
super.addView(mContentContainer, -1, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mIsStatic = true;
}
@Override
protected void drawOverlay(Canvas canvas) {
// NO-OP
}
@Override
protected void onOffsetPixelsChanged(int offsetPixels) {
// NO-OP
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int width = r - l;
final int height = b - t;
switch (getPosition()) {
case LEFT:
mMenuContainer.layout(0, 0, mMenuSize, height);
mContentContainer.layout(mMenuSize, 0, width, height);
break;
case RIGHT:
mMenuContainer.layout(width - mMenuSize, 0, width, height);
mContentContainer.layout(0, 0, width - mMenuSize, height);
break;
case TOP:
mMenuContainer.layout(0, 0, width, mMenuSize);
mContentContainer.layout(0, mMenuSize, width, height);
break;
case BOTTOM:
mMenuContainer.layout(0, height - mMenuSize, width, height);
mContentContainer.layout(0, 0, width, height - mMenuSize);
break;
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
throw new IllegalStateException("Must measure with an exact size");
}
final int width = MeasureSpec.getSize(widthMeasureSpec);
final int height = MeasureSpec.getSize(heightMeasureSpec);
switch (getPosition()) {
case LEFT:
case RIGHT: {
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
final int menuWidth = mMenuSize;
final int menuWidthMeasureSpec = MeasureSpec.makeMeasureSpec(menuWidth, MeasureSpec.EXACTLY);
final int contentWidth = width - menuWidth;
final int contentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY);
mContentContainer.measure(contentWidthMeasureSpec, childHeightMeasureSpec);
mMenuContainer.measure(menuWidthMeasureSpec, childHeightMeasureSpec);
break;
}
case TOP:
case BOTTOM: {
final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
final int menuHeight = mMenuSize;
final int menuHeightMeasureSpec = MeasureSpec.makeMeasureSpec(menuHeight, MeasureSpec.EXACTLY);
final int contentHeight = height - menuHeight;
final int contentHeightMeasureSpec = MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY);
mContentContainer.measure(childWidthMeasureSpec, contentHeightMeasureSpec);
mMenuContainer.measure(childWidthMeasureSpec, menuHeightMeasureSpec);
break;
}
}
setMeasuredDimension(width, height);
}
@Override
public void toggleMenu(boolean animate) {
// NO-OP
}
@Override
public void openMenu(boolean animate) {
// NO-OP
}
@Override
public void closeMenu(boolean animate) {
// NO-OP
}
@Override
public boolean isMenuVisible() {
return true;
}
@Override
public void setMenuSize(int size) {
mMenuSize = size;
requestLayout();
invalidate();
}
@Override
public void setOffsetMenuEnabled(boolean offsetMenu) {
// NO-OP
}
@Override
public boolean getOffsetMenuEnabled() {
return false;
}
@Override
public void peekDrawer() {
// NO-OP
}
@Override
public void peekDrawer(long delay) {
// NO-OP
}
@Override
public void peekDrawer(long startDelay, long delay) {
// NO-OP
}
@Override
public void setHardwareLayerEnabled(boolean enabled) {
// NO-OP
}
@Override
public int getTouchMode() {
return TOUCH_MODE_NONE;
}
@Override
public void setTouchMode(int mode) {
// NO-OP
}
@Override
public void setTouchBezelSize(int size) {
// NO-OP
}
@Override
public int getTouchBezelSize() {
return 0;
}
@Override
public void setSlideDrawable(int drawableRes) {
// NO-OP
}
@Override
public void setSlideDrawable(Drawable drawable) {
// NO-OP
}
@Override
public void setupUpIndicator(Activity activity) {
// NO-OP
}
@Override
public void setDrawerIndicatorEnabled(boolean enabled) {
// NO-OP
}
@Override
public boolean isDrawerIndicatorEnabled() {
return false;
}
}

View File

@ -0,0 +1,50 @@
package net.simonvt.menudrawer;
import android.os.Build;
import android.view.View;
final class ViewHelper {
private ViewHelper() {
}
public static int getLeft(View v) {
if (MenuDrawer.USE_TRANSLATIONS) {
return (int) (v.getLeft() + v.getTranslationX());
}
return v.getLeft();
}
public static int getTop(View v) {
if (MenuDrawer.USE_TRANSLATIONS) {
return (int) (v.getTop() + v.getTranslationY());
}
return v.getTop();
}
public static int getRight(View v) {
if (MenuDrawer.USE_TRANSLATIONS) {
return (int) (v.getRight() + v.getTranslationX());
}
return v.getRight();
}
public static int getBottom(View v) {
if (MenuDrawer.USE_TRANSLATIONS) {
return (int) (v.getBottom() + v.getTranslationY());
}
return v.getBottom();
}
public static int getLayoutDirection(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return v.getLayoutDirection();
}
return View.LAYOUT_DIRECTION_LTR;
}
}

View File

@ -0,0 +1,83 @@
package net.simonvt.menudrawer.compat;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.Log;
import java.lang.reflect.Method;
public final class ActionBarHelper {
private static final String TAG = "ActionBarHelper";
static final boolean DEBUG = false;
private Activity mActivity;
private Object mIndicatorInfo;
private boolean mUsesCompat;
public ActionBarHelper(Activity activity) {
mActivity = activity;
try {
Class clazz = activity.getClass();
Method m = clazz.getMethod("getSupportActionBar");
mUsesCompat = true;
} catch (NoSuchMethodException e) {
if (DEBUG) {
Log.e(TAG,
"Activity " + activity.getClass().getSimpleName() + " does not use a compatibility action bar",
e);
}
}
mIndicatorInfo = getIndicatorInfo();
}
private Object getIndicatorInfo() {
if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return ActionBarHelperCompat.getIndicatorInfo(mActivity);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return ActionBarHelperNative.getIndicatorInfo(mActivity);
}
return null;
}
public void setActionBarUpIndicator(Drawable drawable, int contentDesc) {
if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
ActionBarHelperCompat.setActionBarUpIndicator(mIndicatorInfo, mActivity, drawable, contentDesc);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
ActionBarHelperNative.setActionBarUpIndicator(mIndicatorInfo, mActivity, drawable, contentDesc);
}
}
public void setActionBarDescription(int contentDesc) {
if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
ActionBarHelperCompat.setActionBarDescription(mIndicatorInfo, mActivity, contentDesc);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
ActionBarHelperNative.setActionBarDescription(mIndicatorInfo, mActivity, contentDesc);
}
}
public Drawable getThemeUpIndicator() {
if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return ActionBarHelperCompat.getThemeUpIndicator(mIndicatorInfo);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return ActionBarHelperNative.getThemeUpIndicator(mIndicatorInfo, mActivity);
}
return null;
}
public void setDisplayShowHomeAsUpEnabled(boolean enabled) {
if (mUsesCompat && Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
ActionBarHelperCompat.setDisplayHomeAsUpEnabled(mIndicatorInfo, enabled);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
ActionBarHelperNative.setDisplayHomeAsUpEnabled(mActivity, enabled);
}
}
}

View File

@ -0,0 +1,107 @@
package net.simonvt.menudrawer.compat;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import java.lang.reflect.Method;
final class ActionBarHelperCompat {
private static final String TAG = "ActionBarHelperCompat";
private ActionBarHelperCompat() {
}
public static void setActionBarUpIndicator(Object info, Activity activity, Drawable drawable, int contentDescRes) {
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
if (sii.mUpIndicatorView != null) {
sii.mUpIndicatorView.setImageDrawable(drawable);
final String contentDescription = contentDescRes == 0 ? null : activity.getString(contentDescRes);
sii.mUpIndicatorView.setContentDescription(contentDescription);
}
}
public static void setActionBarDescription(Object info, Activity activity, int contentDescRes) {
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
if (sii.mUpIndicatorView != null) {
final String contentDescription = contentDescRes == 0 ? null : activity.getString(contentDescRes);
sii.mUpIndicatorView.setContentDescription(contentDescription);
}
}
public static Drawable getThemeUpIndicator(Object info) {
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
if (sii.mUpIndicatorView != null) {
return sii.mUpIndicatorView.getDrawable();
}
return null;
}
public static Object getIndicatorInfo(Activity activity) {
return new SetIndicatorInfo(activity);
}
public static void setDisplayHomeAsUpEnabled(Object info, boolean enabled) {
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
if (sii.mHomeAsUpEnabled != null) {
try {
sii.mHomeAsUpEnabled.invoke(sii.mActionBar, enabled);
} catch (Throwable t) {
if (ActionBarHelper.DEBUG) {
Log.e(TAG, "Unable to call setHomeAsUpEnabled", t);
}
}
}
}
private static class SetIndicatorInfo {
public ImageView mUpIndicatorView;
public Object mActionBar;
public Method mHomeAsUpEnabled;
SetIndicatorInfo(Activity activity) {
try {
String appPackage = activity.getPackageName();
try {
// Attempt to find ActionBarSherlock up indicator
final int homeId = activity.getResources().getIdentifier("abs__home", "id", appPackage);
View v = activity.findViewById(homeId);
ViewGroup parent = (ViewGroup) v.getParent();
final int upId = activity.getResources().getIdentifier("abs__up", "id", appPackage);
mUpIndicatorView = (ImageView) parent.findViewById(upId);
} catch (Throwable t) {
if (ActionBarHelper.DEBUG) {
Log.e(TAG, "ABS action bar not found", t);
}
}
if (mUpIndicatorView == null) {
// Attempt to find AppCompat up indicator
final int homeId = activity.getResources().getIdentifier("home", "id", appPackage);
View v = activity.findViewById(homeId);
ViewGroup parent = (ViewGroup) v.getParent();
final int upId = activity.getResources().getIdentifier("up", "id", appPackage);
mUpIndicatorView = (ImageView) parent.findViewById(upId);
}
Class supportActivity = activity.getClass();
Method getActionBar = supportActivity.getMethod("getSupportActionBar");
mActionBar = getActionBar.invoke(activity, null);
Class supportActionBar = mActionBar.getClass();
mHomeAsUpEnabled = supportActionBar.getMethod("setDisplayHomeAsUpEnabled", Boolean.TYPE);
} catch (Throwable t) {
if (ActionBarHelper.DEBUG) {
Log.e(TAG, "Unable to init SetIndicatorInfo for ABS", t);
}
}
}
}
}

View File

@ -0,0 +1,114 @@
package net.simonvt.menudrawer.compat;
import android.app.ActionBar;
import android.app.Activity;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import java.lang.reflect.Method;
final class ActionBarHelperNative {
private static final String TAG = "ActionBarHelperNative";
private ActionBarHelperNative() {
}
private static final int[] THEME_ATTRS = new int[] {
android.R.attr.homeAsUpIndicator
};
public static void setActionBarUpIndicator(Object info, Activity activity, Drawable drawable, int contentDescRes) {
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
if (sii.setHomeAsUpIndicator != null) {
try {
final ActionBar actionBar = activity.getActionBar();
sii.setHomeAsUpIndicator.invoke(actionBar, drawable);
sii.setHomeActionContentDescription.invoke(actionBar, contentDescRes);
} catch (Throwable t) {
if (ActionBarHelper.DEBUG) Log.e(TAG, "Couldn't set home-as-up indicator via JB-MR2 API", t);
}
} else if (sii.upIndicatorView != null) {
sii.upIndicatorView.setImageDrawable(drawable);
} else {
if (ActionBarHelper.DEBUG) Log.e(TAG, "Couldn't set home-as-up indicator");
}
}
public static void setActionBarDescription(Object info, Activity activity, int contentDescRes) {
final SetIndicatorInfo sii = (SetIndicatorInfo) info;
if (sii.setHomeAsUpIndicator != null) {
try {
final ActionBar actionBar = activity.getActionBar();
sii.setHomeActionContentDescription.invoke(actionBar, contentDescRes);
} catch (Throwable t) {
if (ActionBarHelper.DEBUG) Log.e(TAG, "Couldn't set content description via JB-MR2 API", t);
}
}
}
public static Drawable getThemeUpIndicator(Object info, Activity activity) {
final TypedArray a = activity.obtainStyledAttributes(THEME_ATTRS);
final Drawable result = a.getDrawable(0);
a.recycle();
return result;
}
public static Object getIndicatorInfo(Activity activity) {
return new SetIndicatorInfo(activity);
}
public static void setDisplayHomeAsUpEnabled(Activity activity, boolean b) {
ActionBar actionBar = activity.getActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(b);
}
}
private static class SetIndicatorInfo {
public Method setHomeAsUpIndicator;
public Method setHomeActionContentDescription;
public ImageView upIndicatorView;
SetIndicatorInfo(Activity activity) {
try {
setHomeAsUpIndicator = ActionBar.class.getDeclaredMethod("setHomeAsUpIndicator", Drawable.class);
setHomeActionContentDescription = ActionBar.class.getDeclaredMethod(
"setHomeActionContentDescription", Integer.TYPE);
// If we got the method we won't need the stuff below.
return;
} catch (Throwable t) {
// Oh well. We'll use the other mechanism below instead.
}
final View home = activity.findViewById(android.R.id.home);
if (home == null) {
// Action bar doesn't have a known configuration, an OEM messed with things.
return;
}
final ViewGroup parent = (ViewGroup) home.getParent();
final int childCount = parent.getChildCount();
if (childCount != 2) {
// No idea which one will be the right one, an OEM messed with things.
return;
}
final View first = parent.getChildAt(0);
final View second = parent.getChildAt(1);
final View up = first.getId() == android.R.id.home ? second : first;
if (up instanceof ImageView) {
// Jackpot! (Probably...)
upIndicatorView = (ImageView) up;
}
}
}
}

View File

@ -0,0 +1,65 @@
<resources>
<!-- Reference to a style for the menu drawer. -->
<attr name="menuDrawerStyle" format="reference" />
<!-- Styleables used for styling the menu drawer. -->
<declare-styleable name="MenuDrawer">
<!-- Drawable to use for the background of the content. -->
<attr name="mdContentBackground" format="reference" />
<!-- Drawable to use for the background of the menu. -->
<attr name="mdMenuBackground" format="reference" />
<!-- The size of the menu. -->
<attr name="mdMenuSize" format="dimension" />
<!-- Drawable used as indicator for the active view. -->
<attr name="mdActiveIndicator" format="reference" />
<!-- Defines whether the content will have a dropshadow onto the menu. Default is true. -->
<attr name="mdDropShadowEnabled" format="boolean" />
<!-- The size of the drop shadow. Default is 6dp -->
<attr name="mdDropShadowSize" format="dimension" />
<!-- The color of the drop shadow. Default is #FF000000. -->
<attr name="mdDropShadowColor" format="color" />
<!-- Drawable used for the drop shadow. -->
<attr name="mdDropShadow" format="reference" />
<!-- The touch bezel size. -->
<attr name="mdTouchBezelSize" format="dimension" />
<!-- Whether the indicator should be animated between active views. -->
<attr name="mdAllowIndicatorAnimation" format="boolean" />
<!-- The maximum animation duration -->
<attr name="mdMaxAnimationDuration" format="integer" />
<!-- Drawable that replaces the up indicator -->
<attr name="mdSlideDrawable" format="reference" />
<!-- String to use as the up indicators content description when the drawer is open -->
<attr name="mdDrawerOpenUpContentDescription" format="string" />
<!-- String to use as the up indicators content description when the drawer is closed -->
<attr name="mdDrawerClosedUpContentDescription" format="string" />
<!-- Whether an overlay should be drawn as the drawer is opened and closed -->
<attr name="mdDrawOverlay" format="boolean" />
<!-- The position of the drawer -->
<attr name="mdPosition" format="enum">
<enum name="left" value="0" />
<enum name="top" value="1" />
<enum name="right" value="2" />
<enum name="bottom" value="3" />
<enum name="start" value="4" />
<enum name="end" value="5" />
</attr>
</declare-styleable>
</resources>

View File

@ -0,0 +1,6 @@
<resources>
<!-- The default background of the menu. -->
<color name="md__defaultBackground">#FF555555</color>
</resources>

View File

@ -0,0 +1,24 @@
<resources>
<!-- ID used when defining the content layout in XML. -->
<item name="mdContent" type="id" />
<!-- ID used when defining the menu layout in XML. -->
<item name="mdMenu" type="id" />
<!-- The ID of the content container. -->
<item name="md__content" type="id" />
<!-- The ID of the menu container. -->
<item name="md__menu" type="id" />
<!-- The ID of the drawer. -->
<item name="md__drawer" type="id" />
<!-- Used with View#setTag(int) to specify a position for the active view. -->
<item name="mdActiveViewPosition" type="id" />
<item name="md__translationX" type="id" />
<item name="md__translationY" type="id" />
</resources>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="md__drawerOpenIndicatorDesc">Close drawer</string>
<string name="md__drawerClosedIndicatorDesc">Open drawer</string>
</resources>

View File

@ -0,0 +1,13 @@
<resources>
<style name="Widget" />
<!-- Base theme for the menu drawer. -->
<style name="Widget.MenuDrawer">
<item name="mdMenuBackground">@color/md__defaultBackground</item>
<item name="mdContentBackground">?android:attr/windowBackground</item>
<item name="mdDrawerOpenUpContentDescription">@string/md__drawerOpenIndicatorDesc</item>
<item name="mdDrawerClosedUpContentDescription">@string/md__drawerClosedIndicatorDesc</item>
</style>
</resources>

View File

@ -0,0 +1,18 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 22
buildToolsVersion "22.0.1"
defaultConfig {
minSdkVersion 7
targetSdkVersion 22
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}

View File

@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":pulltorefresh" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="Ultrasonic" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="android-gradle" name="Android-Gradle">
<configuration>
<option name="GRADLE_PROJECT_PATH" value=":pulltorefresh" />
</configuration>
</facet>
<facet type="java-gradle" name="Java-Gradle">
<configuration>
<option name="BUILD_FOLDER_PATH" value="$MODULE_DIR$/build" />
<option name="BUILDABLE" value="true" />
</configuration>
</facet>
<facet type="android" name="Android">
<configuration>
<option name="SELECTED_BUILD_VARIANT" value="debug" />
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" />
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" />
<option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugAndroidTestSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" />
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" />
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" />
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" />
<option name="LIBRARY_PROJECT" value="true" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/build/intermediates/classes/debug" />
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/androidTest/debug" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" />
<excludeFolder url="file://$MODULE_DIR$/build/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/outputs" />
<excludeFolder url="file://$MODULE_DIR$/build/tmp" />
</content>
<orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.handmark.pulltorefresh.library"
android:versionCode="2110"
android:versionName="2.1.1">
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="16" />
</manifest>

View File

@ -0,0 +1,57 @@
package com.handmark.pulltorefresh.library;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
public interface ILoadingLayout {
/**
* Set the Last Updated Text. This displayed under the main label when
* Pulling
*
* @param label - Label to set
*/
public void setLastUpdatedLabel(CharSequence label);
/**
* Set the drawable used in the loading layout. This is the same as calling
* <code>setLoadingDrawable(drawable, Mode.BOTH)</code>
*
* @param drawable - Drawable to display
*/
public void setLoadingDrawable(Drawable drawable);
/**
* Set Text to show when the Widget is being Pulled
* <code>setPullLabel(releaseLabel, Mode.BOTH)</code>
*
* @param pullLabel - CharSequence to display
*/
public void setPullLabel(CharSequence pullLabel);
/**
* Set Text to show when the Widget is refreshing
* <code>setRefreshingLabel(releaseLabel, Mode.BOTH)</code>
*
* @param refreshingLabel - CharSequence to display
*/
public void setRefreshingLabel(CharSequence refreshingLabel);
/**
* Set Text to show when the Widget is being pulled, and will refresh when
* released. This is the same as calling
* <code>setReleaseLabel(releaseLabel, Mode.BOTH)</code>
*
* @param releaseLabel - CharSequence to display
*/
public void setReleaseLabel(CharSequence releaseLabel);
/**
* Set's the Sets the typeface and style in which the text should be
* displayed. Please see
* {@link android.widget.TextView#setTypeface(Typeface)
* TextView#setTypeface(Typeface)}.
*/
public void setTextTypeface(Typeface tf);
}

View File

@ -0,0 +1,246 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library;
import android.view.View;
import android.view.animation.Interpolator;
import com.handmark.pulltorefresh.library.PullToRefreshBase.Mode;
import com.handmark.pulltorefresh.library.PullToRefreshBase.OnPullEventListener;
import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener;
import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener2;
import com.handmark.pulltorefresh.library.PullToRefreshBase.State;
public interface IPullToRefresh<T extends View> {
/**
* Demos the Pull-to-Refresh functionality to the user so that they are
* aware it is there. This could be useful when the user first opens your
* app, etc. The animation will only happen if the Refresh View (ListView,
* ScrollView, etc) is in a state where a Pull-to-Refresh could occur by a
* user's touch gesture (i.e. scrolled to the top/bottom).
*
* @return true - if the Demo has been started, false if not.
*/
public boolean demo();
/**
* Get the mode that this view is currently in. This is only really useful
* when using <code>Mode.BOTH</code>.
*
* @return Mode that the view is currently in
*/
public Mode getCurrentMode();
/**
* Returns whether the Touch Events are filtered or not. If true is
* returned, then the View will only use touch events where the difference
* in the Y-axis is greater than the difference in the X-axis. This means
* that the View will not interfere when it is used in a horizontal
* scrolling View (such as a ViewPager).
*
* @return boolean - true if the View is filtering Touch Events
*/
public boolean getFilterTouchEvents();
/**
* Returns a proxy object which allows you to call methods on all of the
* LoadingLayouts (the Views which show when Pulling/Refreshing).
* <p />
* You should not keep the result of this method any longer than you need
* it.
*
* @return Object which will proxy any calls you make on it, to all of the
* LoadingLayouts.
*/
public ILoadingLayout getLoadingLayoutProxy();
/**
* Returns a proxy object which allows you to call methods on the
* LoadingLayouts (the Views which show when Pulling/Refreshing). The actual
* LoadingLayout(s) which will be affected, are chosen by the parameters you
* give.
* <p />
* You should not keep the result of this method any longer than you need
* it.
*
* @param includeStart - Whether to include the Start/Header Views
* @param includeEnd - Whether to include the End/Footer Views
* @return Object which will proxy any calls you make on it, to the
* LoadingLayouts included.
*/
public ILoadingLayout getLoadingLayoutProxy(boolean includeStart, boolean includeEnd);
/**
* Get the mode that this view has been set to. If this returns
* <code>Mode.BOTH</code>, you can use <code>getCurrentMode()</code> to
* check which mode the view is currently in
*
* @return Mode that the view has been set to
*/
public Mode getMode();
/**
* Get the Wrapped Refreshable View. Anything returned here has already been
* added to the content view.
*
* @return The View which is currently wrapped
*/
public T getRefreshableView();
/**
* Get whether the 'Refreshing' View should be automatically shown when
* refreshing. Returns true by default.
*
* @return - true if the Refreshing View will be show
*/
public boolean getShowViewWhileRefreshing();
/**
* @return - The state that the View is currently in.
*/
public State getState();
/**
* Whether Pull-to-Refresh is enabled
*
* @return enabled
*/
public boolean isPullToRefreshEnabled();
/**
* Gets whether Overscroll support is enabled. This is different to
* Android's standard Overscroll support (the edge-glow) which is available
* from GINGERBREAD onwards
*
* @return true - if both PullToRefresh-OverScroll and Android's inbuilt
* OverScroll are enabled
*/
public boolean isPullToRefreshOverScrollEnabled();
/**
* Returns whether the Widget is currently in the Refreshing mState
*
* @return true if the Widget is currently refreshing
*/
public boolean isRefreshing();
/**
* Returns whether the widget has enabled scrolling on the Refreshable View
* while refreshing.
*
* @return true if the widget has enabled scrolling while refreshing
*/
public boolean isScrollingWhileRefreshingEnabled();
/**
* Mark the current Refresh as complete. Will Reset the UI and hide the
* Refreshing View
*/
public void onRefreshComplete();
/**
* Set the Touch Events to be filtered or not. If set to true, then the View
* will only use touch events where the difference in the Y-axis is greater
* than the difference in the X-axis. This means that the View will not
* interfere when it is used in a horizontal scrolling View (such as a
* ViewPager), but will restrict which types of finger scrolls will trigger
* the View.
*
* @param filterEvents - true if you want to filter Touch Events. Default is
* true.
*/
public void setFilterTouchEvents(boolean filterEvents);
/**
* Set the mode of Pull-to-Refresh that this view will use.
*
* @param mode - Mode to set the View to
*/
public void setMode(Mode mode);
/**
* Set OnPullEventListener for the Widget
*
* @param listener - Listener to be used when the Widget has a pull event to
* propogate.
*/
public void setOnPullEventListener(OnPullEventListener<T> listener);
/**
* Set OnRefreshListener for the Widget
*
* @param listener - Listener to be used when the Widget is set to Refresh
*/
public void setOnRefreshListener(OnRefreshListener<T> listener);
/**
* Set OnRefreshListener for the Widget
*
* @param listener - Listener to be used when the Widget is set to Refresh
*/
public void setOnRefreshListener(OnRefreshListener2<T> listener);
/**
* Sets whether Overscroll support is enabled. This is different to
* Android's standard Overscroll support (the edge-glow). This setting only
* takes effect when running on device with Android v2.3 or greater.
*
* @param enabled - true if you want Overscroll enabled
*/
public void setPullToRefreshOverScrollEnabled(boolean enabled);
/**
* Sets the Widget to be in the refresh state. The UI will be updated to
* show the 'Refreshing' view, and be scrolled to show such.
*/
public void setRefreshing();
/**
* Sets the Widget to be in the refresh state. The UI will be updated to
* show the 'Refreshing' view.
*
* @param doScroll - true if you want to force a scroll to the Refreshing
* view.
*/
public void setRefreshing(boolean doScroll);
/**
* Sets the Animation Interpolator that is used for animated scrolling.
* Defaults to a DecelerateInterpolator
*
* @param interpolator - Interpolator to use
*/
public void setScrollAnimationInterpolator(Interpolator interpolator);
/**
* By default the Widget disables scrolling on the Refreshable View while
* refreshing. This method can change this behaviour.
*
* @param scrollingWhileRefreshingEnabled - true if you want to enable
* scrolling while refreshing
*/
public void setScrollingWhileRefreshingEnabled(boolean scrollingWhileRefreshingEnabled);
/**
* A mutator to enable/disable whether the 'Refreshing' View should be
* automatically shown when refreshing.
*
* @param showView
*/
public void setShowViewWhileRefreshing(boolean showView);
}

View File

@ -0,0 +1,73 @@
package com.handmark.pulltorefresh.library;
import java.util.HashSet;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import com.handmark.pulltorefresh.library.internal.LoadingLayout;
public class LoadingLayoutProxy implements ILoadingLayout {
private final HashSet<LoadingLayout> mLoadingLayouts;
LoadingLayoutProxy() {
mLoadingLayouts = new HashSet<LoadingLayout>();
}
/**
* This allows you to add extra LoadingLayout instances to this proxy. This
* is only necessary if you keep your own instances, and want to have them
* included in any
* {@link PullToRefreshBase#createLoadingLayoutProxy(boolean, boolean)
* createLoadingLayoutProxy(...)} calls.
*
* @param layout - LoadingLayout to have included.
*/
public void addLayout(LoadingLayout layout) {
if (null != layout) {
mLoadingLayouts.add(layout);
}
}
@Override
public void setLastUpdatedLabel(CharSequence label) {
for (LoadingLayout layout : mLoadingLayouts) {
layout.setLastUpdatedLabel(label);
}
}
@Override
public void setLoadingDrawable(Drawable drawable) {
for (LoadingLayout layout : mLoadingLayouts) {
layout.setLoadingDrawable(drawable);
}
}
@Override
public void setRefreshingLabel(CharSequence refreshingLabel) {
for (LoadingLayout layout : mLoadingLayouts) {
layout.setRefreshingLabel(refreshingLabel);
}
}
@Override
public void setPullLabel(CharSequence label) {
for (LoadingLayout layout : mLoadingLayouts) {
layout.setPullLabel(label);
}
}
@Override
public void setReleaseLabel(CharSequence label) {
for (LoadingLayout layout : mLoadingLayouts) {
layout.setReleaseLabel(label);
}
}
public void setTextTypeface(Typeface tf) {
for (LoadingLayout layout : mLoadingLayouts) {
layout.setTextTypeface(tf);
}
}
}

View File

@ -0,0 +1,178 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library;
import android.annotation.TargetApi;
import android.util.Log;
import android.view.View;
import com.handmark.pulltorefresh.library.PullToRefreshBase.Mode;
import com.handmark.pulltorefresh.library.PullToRefreshBase.State;
@TargetApi(9)
public final class OverscrollHelper {
static final String LOG_TAG = "OverscrollHelper";
static final float DEFAULT_OVERSCROLL_SCALE = 1f;
/**
* Helper method for Overscrolling that encapsulates all of the necessary
* function.
* <p/>
* This should only be used on AdapterView's such as ListView as it just
* calls through to overScrollBy() with the scrollRange = 0. AdapterView's
* do not have a scroll range (i.e. getScrollY() doesn't work).
*
* @param view - PullToRefreshView that is calling this.
* @param deltaX - Change in X in pixels, passed through from from
* overScrollBy call
* @param scrollX - Current X scroll value in pixels before applying deltaY,
* passed through from from overScrollBy call
* @param deltaY - Change in Y in pixels, passed through from from
* overScrollBy call
* @param scrollY - Current Y scroll value in pixels before applying deltaY,
* passed through from from overScrollBy call
* @param isTouchEvent - true if this scroll operation is the result of a
* touch event, passed through from from overScrollBy call
*/
public static void overScrollBy(final PullToRefreshBase<?> view, final int deltaX, final int scrollX,
final int deltaY, final int scrollY, final boolean isTouchEvent) {
overScrollBy(view, deltaX, scrollX, deltaY, scrollY, 0, isTouchEvent);
}
/**
* Helper method for Overscrolling that encapsulates all of the necessary
* function. This version of the call is used for Views that need to specify
* a Scroll Range but scroll back to it's edge correctly.
*
* @param view - PullToRefreshView that is calling this.
* @param deltaX - Change in X in pixels, passed through from from
* overScrollBy call
* @param scrollX - Current X scroll value in pixels before applying deltaY,
* passed through from from overScrollBy call
* @param deltaY - Change in Y in pixels, passed through from from
* overScrollBy call
* @param scrollY - Current Y scroll value in pixels before applying deltaY,
* passed through from from overScrollBy call
* @param scrollRange - Scroll Range of the View, specifically needed for
* ScrollView
* @param isTouchEvent - true if this scroll operation is the result of a
* touch event, passed through from from overScrollBy call
*/
public static void overScrollBy(final PullToRefreshBase<?> view, final int deltaX, final int scrollX,
final int deltaY, final int scrollY, final int scrollRange, final boolean isTouchEvent) {
overScrollBy(view, deltaX, scrollX, deltaY, scrollY, scrollRange, 0, DEFAULT_OVERSCROLL_SCALE, isTouchEvent);
}
/**
* Helper method for Overscrolling that encapsulates all of the necessary
* function. This is the advanced version of the call.
*
* @param view - PullToRefreshView that is calling this.
* @param deltaX - Change in X in pixels, passed through from from
* overScrollBy call
* @param scrollX - Current X scroll value in pixels before applying deltaY,
* passed through from from overScrollBy call
* @param deltaY - Change in Y in pixels, passed through from from
* overScrollBy call
* @param scrollY - Current Y scroll value in pixels before applying deltaY,
* passed through from from overScrollBy call
* @param scrollRange - Scroll Range of the View, specifically needed for
* ScrollView
* @param fuzzyThreshold - Threshold for which the values how fuzzy we
* should treat the other values. Needed for WebView as it
* doesn't always scroll back to it's edge. 0 = no fuzziness.
* @param scaleFactor - Scale Factor for overscroll amount
* @param isTouchEvent - true if this scroll operation is the result of a
* touch event, passed through from from overScrollBy call
*/
public static void overScrollBy(final PullToRefreshBase<?> view, final int deltaX, final int scrollX,
final int deltaY, final int scrollY, final int scrollRange, final int fuzzyThreshold,
final float scaleFactor, final boolean isTouchEvent) {
final int deltaValue, currentScrollValue, scrollValue;
switch (view.getPullToRefreshScrollDirection()) {
case HORIZONTAL:
deltaValue = deltaX;
scrollValue = scrollX;
currentScrollValue = view.getScrollX();
break;
case VERTICAL:
default:
deltaValue = deltaY;
scrollValue = scrollY;
currentScrollValue = view.getScrollY();
break;
}
// Check that OverScroll is enabled and that we're not currently
// refreshing.
if (view.isPullToRefreshOverScrollEnabled() && !view.isRefreshing()) {
final Mode mode = view.getMode();
// Check that Pull-to-Refresh is enabled, and the event isn't from
// touch
if (mode.permitsPullToRefresh() && !isTouchEvent && deltaValue != 0) {
final int newScrollValue = (deltaValue + scrollValue);
if (PullToRefreshBase.DEBUG) {
Log.d(LOG_TAG, "OverScroll. DeltaX: " + deltaX + ", ScrollX: " + scrollX + ", DeltaY: " + deltaY
+ ", ScrollY: " + scrollY + ", NewY: " + newScrollValue + ", ScrollRange: " + scrollRange
+ ", CurrentScroll: " + currentScrollValue);
}
if (newScrollValue < (0 - fuzzyThreshold)) {
// Check the mode supports the overscroll direction, and
// then move scroll
if (mode.showHeaderLoadingLayout()) {
// If we're currently at zero, we're about to start
// overscrolling, so change the state
if (currentScrollValue == 0) {
view.setState(State.OVERSCROLLING);
}
view.setHeaderScroll((int) (scaleFactor * (currentScrollValue + newScrollValue)));
}
} else if (newScrollValue > (scrollRange + fuzzyThreshold)) {
// Check the mode supports the overscroll direction, and
// then move scroll
if (mode.showFooterLoadingLayout()) {
// If we're currently at zero, we're about to start
// overscrolling, so change the state
if (currentScrollValue == 0) {
view.setState(State.OVERSCROLLING);
}
view.setHeaderScroll((int) (scaleFactor * (currentScrollValue + newScrollValue - scrollRange)));
}
} else if (Math.abs(newScrollValue) <= fuzzyThreshold
|| Math.abs(newScrollValue - scrollRange) <= fuzzyThreshold) {
// Means we've stopped overscrolling, so scroll back to 0
view.setState(State.RESET);
}
} else if (isTouchEvent && State.OVERSCROLLING == view.getState()) {
// This condition means that we were overscrolling from a fling,
// but the user has touched the View and is now overscrolling
// from touch instead. We need to just reset.
view.setState(State.RESET);
}
}
}
static boolean isAndroidOverScrollEnabled(View view) {
return view.getOverScrollMode() != View.OVER_SCROLL_NEVER;
}
}

View File

@ -0,0 +1,475 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import com.handmark.pulltorefresh.library.internal.EmptyViewMethodAccessor;
import com.handmark.pulltorefresh.library.internal.IndicatorLayout;
public abstract class PullToRefreshAdapterViewBase<T extends AbsListView> extends PullToRefreshBase<T> implements
OnScrollListener {
private static FrameLayout.LayoutParams convertEmptyViewLayoutParams(ViewGroup.LayoutParams lp) {
FrameLayout.LayoutParams newLp = null;
if (null != lp) {
newLp = new FrameLayout.LayoutParams(lp);
if (lp instanceof LinearLayout.LayoutParams) {
newLp.gravity = ((LinearLayout.LayoutParams) lp).gravity;
} else {
newLp.gravity = Gravity.CENTER;
}
}
return newLp;
}
private boolean mLastItemVisible;
private OnScrollListener mOnScrollListener;
private OnLastItemVisibleListener mOnLastItemVisibleListener;
private View mEmptyView;
private IndicatorLayout mIndicatorIvTop;
private IndicatorLayout mIndicatorIvBottom;
private boolean mShowIndicator;
private boolean mScrollEmptyView = true;
public PullToRefreshAdapterViewBase(Context context) {
super(context);
mRefreshableView.setOnScrollListener(this);
}
public PullToRefreshAdapterViewBase(Context context, AttributeSet attrs) {
super(context, attrs);
mRefreshableView.setOnScrollListener(this);
}
public PullToRefreshAdapterViewBase(Context context, Mode mode) {
super(context, mode);
mRefreshableView.setOnScrollListener(this);
}
public PullToRefreshAdapterViewBase(Context context, Mode mode, AnimationStyle animStyle) {
super(context, mode, animStyle);
mRefreshableView.setOnScrollListener(this);
}
/**
* Gets whether an indicator graphic should be displayed when the View is in
* a state where a Pull-to-Refresh can happen. An example of this state is
* when the Adapter View is scrolled to the top and the mode is set to
* {@link Mode#PULL_FROM_START}. The default value is <var>true</var> if
* {@link PullToRefreshBase#isPullToRefreshOverScrollEnabled()
* isPullToRefreshOverScrollEnabled()} returns false.
*
* @return true if the indicators will be shown
*/
public boolean getShowIndicator() {
return mShowIndicator;
}
public final void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount,
final int totalItemCount) {
if (DEBUG) {
Log.d(LOG_TAG, "First Visible: " + firstVisibleItem + ". Visible Count: " + visibleItemCount
+ ". Total Items:" + totalItemCount);
}
/**
* Set whether the Last Item is Visible. lastVisibleItemIndex is a
* zero-based index, so we minus one totalItemCount to check
*/
if (null != mOnLastItemVisibleListener) {
mLastItemVisible = (totalItemCount > 0) && (firstVisibleItem + visibleItemCount >= totalItemCount - 1);
}
// If we're showing the indicator, check positions...
if (getShowIndicatorInternal()) {
updateIndicatorViewsVisibility();
}
// Finally call OnScrollListener if we have one
if (null != mOnScrollListener) {
mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
}
public final void onScrollStateChanged(final AbsListView view, final int state) {
/**
* Check that the scrolling has stopped, and that the last item is
* visible.
*/
if (state == OnScrollListener.SCROLL_STATE_IDLE && null != mOnLastItemVisibleListener && mLastItemVisible) {
mOnLastItemVisibleListener.onLastItemVisible();
}
if (null != mOnScrollListener) {
mOnScrollListener.onScrollStateChanged(view, state);
}
}
/**
* Pass-through method for {@link PullToRefreshBase#getRefreshableView()
* getRefreshableView()}.
* {@link AdapterView#setAdapter(android.widget.Adapter)}
* setAdapter(adapter)}. This is just for convenience!
*
* @param adapter - Adapter to set
*/
public void setAdapter(ListAdapter adapter) {
((AdapterView<ListAdapter>) mRefreshableView).setAdapter(adapter);
}
/**
* Sets the Empty View to be used by the Adapter View.
* <p/>
* We need it handle it ourselves so that we can Pull-to-Refresh when the
* Empty View is shown.
* <p/>
* Please note, you do <strong>not</strong> usually need to call this method
* yourself. Calling setEmptyView on the AdapterView will automatically call
* this method and set everything up. This includes when the Android
* Framework automatically sets the Empty View based on it's ID.
*
* @param newEmptyView - Empty View to be used
*/
public final void setEmptyView(View newEmptyView) {
FrameLayout refreshableViewWrapper = getRefreshableViewWrapper();
if (null != newEmptyView) {
// New view needs to be clickable so that Android recognizes it as a
// target for Touch Events
newEmptyView.setClickable(true);
ViewParent newEmptyViewParent = newEmptyView.getParent();
if (null != newEmptyViewParent && newEmptyViewParent instanceof ViewGroup) {
((ViewGroup) newEmptyViewParent).removeView(newEmptyView);
}
// We need to convert any LayoutParams so that it works in our
// FrameLayout
FrameLayout.LayoutParams lp = convertEmptyViewLayoutParams(newEmptyView.getLayoutParams());
if (null != lp) {
refreshableViewWrapper.addView(newEmptyView, lp);
} else {
refreshableViewWrapper.addView(newEmptyView);
}
}
if (mRefreshableView instanceof EmptyViewMethodAccessor) {
((EmptyViewMethodAccessor) mRefreshableView).setEmptyViewInternal(newEmptyView);
} else {
mRefreshableView.setEmptyView(newEmptyView);
}
mEmptyView = newEmptyView;
}
/**
* Pass-through method for {@link PullToRefreshBase#getRefreshableView()
* getRefreshableView()}.
* {@link AdapterView#setOnItemClickListener(OnItemClickListener)
* setOnItemClickListener(listener)}. This is just for convenience!
*
* @param listener - OnItemClickListener to use
*/
public void setOnItemClickListener(OnItemClickListener listener) {
mRefreshableView.setOnItemClickListener(listener);
}
public final void setOnLastItemVisibleListener(OnLastItemVisibleListener listener) {
mOnLastItemVisibleListener = listener;
}
public final void setOnScrollListener(OnScrollListener listener) {
mOnScrollListener = listener;
}
public final void setScrollEmptyView(boolean doScroll) {
mScrollEmptyView = doScroll;
}
/**
* Sets whether an indicator graphic should be displayed when the View is in
* a state where a Pull-to-Refresh can happen. An example of this state is
* when the Adapter View is scrolled to the top and the mode is set to
* {@link Mode#PULL_FROM_START}
*
* @param showIndicator - true if the indicators should be shown.
*/
public void setShowIndicator(boolean showIndicator) {
mShowIndicator = showIndicator;
if (getShowIndicatorInternal()) {
// If we're set to Show Indicator, add/update them
addIndicatorViews();
} else {
// If not, then remove then
removeIndicatorViews();
}
}
;
@Override
protected void onPullToRefresh() {
super.onPullToRefresh();
if (getShowIndicatorInternal()) {
switch (getCurrentMode()) {
case PULL_FROM_END:
mIndicatorIvBottom.pullToRefresh();
break;
case PULL_FROM_START:
mIndicatorIvTop.pullToRefresh();
break;
default:
// NO-OP
break;
}
}
}
protected void onRefreshing(boolean doScroll) {
super.onRefreshing(doScroll);
if (getShowIndicatorInternal()) {
updateIndicatorViewsVisibility();
}
}
@Override
protected void onReleaseToRefresh() {
super.onReleaseToRefresh();
if (getShowIndicatorInternal()) {
switch (getCurrentMode()) {
case PULL_FROM_END:
mIndicatorIvBottom.releaseToRefresh();
break;
case PULL_FROM_START:
mIndicatorIvTop.releaseToRefresh();
break;
default:
// NO-OP
break;
}
}
}
@Override
protected void onReset() {
super.onReset();
if (getShowIndicatorInternal()) {
updateIndicatorViewsVisibility();
}
}
@Override
protected void handleStyledAttributes(TypedArray a) {
// Set Show Indicator to the XML value, or default value
mShowIndicator = a.getBoolean(R.styleable.PullToRefresh_ptrShowIndicator, !isPullToRefreshOverScrollEnabled());
}
protected boolean isReadyForPullStart() {
return isFirstItemVisible();
}
protected boolean isReadyForPullEnd() {
return isLastItemVisible();
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (null != mEmptyView && !mScrollEmptyView) {
mEmptyView.scrollTo(-l, -t);
}
}
@Override
protected void updateUIForMode() {
super.updateUIForMode();
// Check Indicator Views consistent with new Mode
if (getShowIndicatorInternal()) {
addIndicatorViews();
} else {
removeIndicatorViews();
}
}
private void addIndicatorViews() {
Mode mode = getMode();
FrameLayout refreshableViewWrapper = getRefreshableViewWrapper();
if (mode.showHeaderLoadingLayout() && null == mIndicatorIvTop) {
// If the mode can pull down, and we don't have one set already
mIndicatorIvTop = new IndicatorLayout(getContext(), Mode.PULL_FROM_START);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.rightMargin = getResources().getDimensionPixelSize(R.dimen.indicator_right_padding);
params.gravity = Gravity.TOP | Gravity.RIGHT;
refreshableViewWrapper.addView(mIndicatorIvTop, params);
} else if (!mode.showHeaderLoadingLayout() && null != mIndicatorIvTop) {
// If we can't pull down, but have a View then remove it
refreshableViewWrapper.removeView(mIndicatorIvTop);
mIndicatorIvTop = null;
}
if (mode.showFooterLoadingLayout() && null == mIndicatorIvBottom) {
// If the mode can pull down, and we don't have one set already
mIndicatorIvBottom = new IndicatorLayout(getContext(), Mode.PULL_FROM_END);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.rightMargin = getResources().getDimensionPixelSize(R.dimen.indicator_right_padding);
params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
refreshableViewWrapper.addView(mIndicatorIvBottom, params);
} else if (!mode.showFooterLoadingLayout() && null != mIndicatorIvBottom) {
// If we can't pull down, but have a View then remove it
refreshableViewWrapper.removeView(mIndicatorIvBottom);
mIndicatorIvBottom = null;
}
}
private boolean getShowIndicatorInternal() {
return mShowIndicator && isPullToRefreshEnabled();
}
private boolean isFirstItemVisible() {
final Adapter adapter = mRefreshableView.getAdapter();
if (null == adapter || adapter.isEmpty()) {
if (DEBUG) {
Log.d(LOG_TAG, "isFirstItemVisible. Empty View.");
}
return true;
} else {
/**
* This check should really just be:
* mRefreshableView.getFirstVisiblePosition() == 0, but PtRListView
* internally use a HeaderView which messes the positions up. For
* now we'll just add one to account for it and rely on the inner
* condition which checks getTop().
*/
if (mRefreshableView.getFirstVisiblePosition() <= 1) {
final View firstVisibleChild = mRefreshableView.getChildAt(0);
if (firstVisibleChild != null) {
return firstVisibleChild.getTop() >= mRefreshableView.getTop();
}
}
}
return false;
}
private boolean isLastItemVisible() {
final Adapter adapter = mRefreshableView.getAdapter();
if (null == adapter || adapter.isEmpty()) {
if (DEBUG) {
Log.d(LOG_TAG, "isLastItemVisible. Empty View.");
}
return true;
} else {
final int lastItemPosition = mRefreshableView.getCount() - 1;
final int lastVisiblePosition = mRefreshableView.getLastVisiblePosition();
if (DEBUG) {
Log.d(LOG_TAG, "isLastItemVisible. Last Item Position: " + lastItemPosition + " Last Visible Pos: "
+ lastVisiblePosition);
}
/**
* This check should really just be: lastVisiblePosition ==
* lastItemPosition, but PtRListView internally uses a FooterView
* which messes the positions up. For me we'll just subtract one to
* account for it and rely on the inner condition which checks
* getBottom().
*/
if (lastVisiblePosition >= lastItemPosition - 1) {
final int childIndex = lastVisiblePosition - mRefreshableView.getFirstVisiblePosition();
final View lastVisibleChild = mRefreshableView.getChildAt(childIndex);
if (lastVisibleChild != null) {
return lastVisibleChild.getBottom() <= mRefreshableView.getBottom();
}
}
}
return false;
}
private void removeIndicatorViews() {
if (null != mIndicatorIvTop) {
getRefreshableViewWrapper().removeView(mIndicatorIvTop);
mIndicatorIvTop = null;
}
if (null != mIndicatorIvBottom) {
getRefreshableViewWrapper().removeView(mIndicatorIvBottom);
mIndicatorIvBottom = null;
}
}
private void updateIndicatorViewsVisibility() {
if (null != mIndicatorIvTop) {
if (!isRefreshing() && isReadyForPullStart()) {
if (!mIndicatorIvTop.isVisible()) {
mIndicatorIvTop.show();
}
} else {
if (mIndicatorIvTop.isVisible()) {
mIndicatorIvTop.hide();
}
}
}
if (null != mIndicatorIvBottom) {
if (!isRefreshing() && isReadyForPullEnd()) {
if (!mIndicatorIvBottom.isVisible()) {
mIndicatorIvBottom.show();
}
} else {
if (mIndicatorIvBottom.isVisible()) {
mIndicatorIvBottom.hide();
}
}
}
}
}

View File

@ -0,0 +1,103 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ExpandableListView;
import com.handmark.pulltorefresh.library.internal.EmptyViewMethodAccessor;
public class PullToRefreshExpandableListView extends PullToRefreshAdapterViewBase<ExpandableListView> {
public PullToRefreshExpandableListView(Context context) {
super(context);
}
public PullToRefreshExpandableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PullToRefreshExpandableListView(Context context, Mode mode) {
super(context, mode);
}
public PullToRefreshExpandableListView(Context context, Mode mode, AnimationStyle style) {
super(context, mode, style);
}
@Override
public final Orientation getPullToRefreshScrollDirection() {
return Orientation.VERTICAL;
}
@Override
protected ExpandableListView createRefreshableView(Context context, AttributeSet attrs) {
final ExpandableListView lv;
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
lv = new InternalExpandableListViewSDK9(context, attrs);
} else {
lv = new InternalExpandableListView(context, attrs);
}
// Set it to this so it can be used in ListActivity/ListFragment
lv.setId(android.R.id.list);
return lv;
}
class InternalExpandableListView extends ExpandableListView implements EmptyViewMethodAccessor {
public InternalExpandableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setEmptyView(View emptyView) {
PullToRefreshExpandableListView.this.setEmptyView(emptyView);
}
@Override
public void setEmptyViewInternal(View emptyView) {
super.setEmptyView(emptyView);
}
}
@TargetApi(9)
final class InternalExpandableListViewSDK9 extends InternalExpandableListView {
public InternalExpandableListViewSDK9(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
// Does all of the hard work...
OverscrollHelper.overScrollBy(PullToRefreshExpandableListView.this, deltaX, scrollX, deltaY, scrollY,
isTouchEvent);
return returnValue;
}
}
}

View File

@ -0,0 +1,102 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.view.View;
import android.widget.GridView;
import com.handmark.pulltorefresh.library.internal.EmptyViewMethodAccessor;
public class PullToRefreshGridView extends PullToRefreshAdapterViewBase<GridView> {
public PullToRefreshGridView(Context context) {
super(context);
}
public PullToRefreshGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PullToRefreshGridView(Context context, Mode mode) {
super(context, mode);
}
public PullToRefreshGridView(Context context, Mode mode, AnimationStyle style) {
super(context, mode, style);
}
@Override
public final Orientation getPullToRefreshScrollDirection() {
return Orientation.VERTICAL;
}
@Override
protected final GridView createRefreshableView(Context context, AttributeSet attrs) {
final GridView gv;
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
gv = new InternalGridViewSDK9(context, attrs);
} else {
gv = new InternalGridView(context, attrs);
}
// Use Generated ID (from res/values/ids.xml)
gv.setId(R.id.gridview);
return gv;
}
class InternalGridView extends GridView implements EmptyViewMethodAccessor {
public InternalGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setEmptyView(View emptyView) {
PullToRefreshGridView.this.setEmptyView(emptyView);
}
@Override
public void setEmptyViewInternal(View emptyView) {
super.setEmptyView(emptyView);
}
}
@TargetApi(9)
final class InternalGridViewSDK9 extends InternalGridView {
public InternalGridViewSDK9(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
// Does all of the hard work...
OverscrollHelper.overScrollBy(PullToRefreshGridView.this, deltaX, scrollX, deltaY, scrollY, isTouchEvent);
return returnValue;
}
}
}

View File

@ -0,0 +1,110 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.view.View;
import android.widget.HorizontalScrollView;
public class PullToRefreshHorizontalScrollView extends PullToRefreshBase<HorizontalScrollView> {
public PullToRefreshHorizontalScrollView(Context context) {
super(context);
}
public PullToRefreshHorizontalScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PullToRefreshHorizontalScrollView(Context context, Mode mode) {
super(context, mode);
}
public PullToRefreshHorizontalScrollView(Context context, Mode mode, AnimationStyle style) {
super(context, mode, style);
}
@Override
public final Orientation getPullToRefreshScrollDirection() {
return Orientation.HORIZONTAL;
}
@Override
protected HorizontalScrollView createRefreshableView(Context context, AttributeSet attrs) {
HorizontalScrollView scrollView;
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
scrollView = new InternalHorizontalScrollViewSDK9(context, attrs);
} else {
scrollView = new HorizontalScrollView(context, attrs);
}
scrollView.setId(R.id.scrollview);
return scrollView;
}
@Override
protected boolean isReadyForPullStart() {
return mRefreshableView.getScrollX() == 0;
}
@Override
protected boolean isReadyForPullEnd() {
View scrollViewChild = mRefreshableView.getChildAt(0);
if (null != scrollViewChild) {
return mRefreshableView.getScrollX() >= (scrollViewChild.getWidth() - getWidth());
}
return false;
}
@TargetApi(9)
final class InternalHorizontalScrollViewSDK9 extends HorizontalScrollView {
public InternalHorizontalScrollViewSDK9(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
// Does all of the hard work...
OverscrollHelper.overScrollBy(PullToRefreshHorizontalScrollView.this, deltaX, scrollX, deltaY, scrollY,
getScrollRange(), isTouchEvent);
return returnValue;
}
/**
* Taken from the AOSP ScrollView source
*/
private int getScrollRange() {
int scrollRange = 0;
if (getChildCount() > 0) {
View child = getChildAt(0);
scrollRange = Math.max(0, child.getWidth() - (getWidth() - getPaddingLeft() - getPaddingRight()));
}
return scrollRange;
}
}
}

View File

@ -0,0 +1,337 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import com.handmark.pulltorefresh.library.internal.EmptyViewMethodAccessor;
import com.handmark.pulltorefresh.library.internal.LoadingLayout;
public class PullToRefreshListView extends PullToRefreshAdapterViewBase<ListView> {
private LoadingLayout mHeaderLoadingView;
private LoadingLayout mFooterLoadingView;
private FrameLayout mLvFooterLoadingFrame;
private boolean mListViewExtrasEnabled;
public PullToRefreshListView(Context context) {
super(context);
}
public PullToRefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PullToRefreshListView(Context context, Mode mode) {
super(context, mode);
}
public PullToRefreshListView(Context context, Mode mode, AnimationStyle style) {
super(context, mode, style);
}
@Override
public final Orientation getPullToRefreshScrollDirection() {
return Orientation.VERTICAL;
}
@Override
protected void onRefreshing(final boolean doScroll) {
/**
* If we're not showing the Refreshing view, or the list is empty, the
* the header/footer views won't show so we use the normal method.
*/
ListAdapter adapter = mRefreshableView.getAdapter();
if (!mListViewExtrasEnabled || !getShowViewWhileRefreshing() || null == adapter || adapter.isEmpty()) {
super.onRefreshing(doScroll);
return;
}
super.onRefreshing(false);
final LoadingLayout origLoadingView, listViewLoadingView, oppositeListViewLoadingView;
final int selection, scrollToY;
switch (getCurrentMode()) {
case MANUAL_REFRESH_ONLY:
case PULL_FROM_END:
origLoadingView = getFooterLayout();
listViewLoadingView = mFooterLoadingView;
oppositeListViewLoadingView = mHeaderLoadingView;
selection = mRefreshableView.getCount() - 1;
scrollToY = getScrollY() - getFooterSize();
break;
case PULL_FROM_START:
default:
origLoadingView = getHeaderLayout();
listViewLoadingView = mHeaderLoadingView;
oppositeListViewLoadingView = mFooterLoadingView;
selection = 0;
scrollToY = getScrollY() + getHeaderSize();
break;
}
// Hide our original Loading View
origLoadingView.reset();
origLoadingView.hideAllViews();
// Make sure the opposite end is hidden too
oppositeListViewLoadingView.setVisibility(View.GONE);
// Show the ListView Loading View and set it to refresh.
listViewLoadingView.setVisibility(View.VISIBLE);
listViewLoadingView.refreshing();
if (doScroll) {
// We need to disable the automatic visibility changes for now
disableLoadingLayoutVisibilityChanges();
// We scroll slightly so that the ListView's header/footer is at the
// same Y position as our normal header/footer
setHeaderScroll(scrollToY);
// Make sure the ListView is scrolled to show the loading
// header/footer
mRefreshableView.setSelection(selection);
// Smooth scroll as normal
smoothScrollTo(0);
}
}
@Override
protected void onReset() {
/**
* If the extras are not enabled, just call up to super and return.
*/
if (!mListViewExtrasEnabled) {
super.onReset();
return;
}
final LoadingLayout originalLoadingLayout, listViewLoadingLayout;
final int scrollToHeight, selection;
final boolean scrollLvToEdge;
switch (getCurrentMode()) {
case MANUAL_REFRESH_ONLY:
case PULL_FROM_END:
originalLoadingLayout = getFooterLayout();
listViewLoadingLayout = mFooterLoadingView;
selection = mRefreshableView.getCount() - 1;
scrollToHeight = getFooterSize();
scrollLvToEdge = Math.abs(mRefreshableView.getLastVisiblePosition() - selection) <= 1;
break;
case PULL_FROM_START:
default:
originalLoadingLayout = getHeaderLayout();
listViewLoadingLayout = mHeaderLoadingView;
scrollToHeight = -getHeaderSize();
selection = 0;
scrollLvToEdge = Math.abs(mRefreshableView.getFirstVisiblePosition() - selection) <= 1;
break;
}
// If the ListView header loading layout is showing, then we need to
// flip so that the original one is showing instead
if (listViewLoadingLayout.getVisibility() == View.VISIBLE) {
// Set our Original View to Visible
originalLoadingLayout.showInvisibleViews();
// Hide the ListView Header/Footer
listViewLoadingLayout.setVisibility(View.GONE);
/**
* Scroll so the View is at the same Y as the ListView
* header/footer, but only scroll if: we've pulled to refresh, it's
* positioned correctly
*/
if (scrollLvToEdge && getState() != State.MANUAL_REFRESHING) {
mRefreshableView.setSelection(selection);
setHeaderScroll(scrollToHeight);
}
}
// Finally, call up to super
super.onReset();
}
@Override
protected LoadingLayoutProxy createLoadingLayoutProxy(final boolean includeStart, final boolean includeEnd) {
LoadingLayoutProxy proxy = super.createLoadingLayoutProxy(includeStart, includeEnd);
if (mListViewExtrasEnabled) {
final Mode mode = getMode();
if (includeStart && mode.showHeaderLoadingLayout()) {
proxy.addLayout(mHeaderLoadingView);
}
if (includeEnd && mode.showFooterLoadingLayout()) {
proxy.addLayout(mFooterLoadingView);
}
}
return proxy;
}
protected ListView createListView(Context context, AttributeSet attrs) {
final ListView lv;
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
lv = new InternalListViewSDK9(context, attrs);
} else {
lv = new InternalListView(context, attrs);
}
return lv;
}
@Override
protected ListView createRefreshableView(Context context, AttributeSet attrs) {
ListView lv = createListView(context, attrs);
// Set it to this so it can be used in ListActivity/ListFragment
lv.setId(android.R.id.list);
return lv;
}
@Override
protected void handleStyledAttributes(TypedArray a) {
super.handleStyledAttributes(a);
mListViewExtrasEnabled = a.getBoolean(R.styleable.PullToRefresh_ptrListViewExtrasEnabled, true);
if (mListViewExtrasEnabled) {
final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL);
// Create Loading Views ready for use later
FrameLayout frame = new FrameLayout(getContext());
mHeaderLoadingView = createLoadingLayout(getContext(), Mode.PULL_FROM_START, a);
mHeaderLoadingView.setVisibility(View.GONE);
frame.addView(mHeaderLoadingView, lp);
mRefreshableView.addHeaderView(frame, null, false);
mLvFooterLoadingFrame = new FrameLayout(getContext());
mFooterLoadingView = createLoadingLayout(getContext(), Mode.PULL_FROM_END, a);
mFooterLoadingView.setVisibility(View.GONE);
mLvFooterLoadingFrame.addView(mFooterLoadingView, lp);
/**
* If the value for Scrolling While Refreshing hasn't been
* explicitly set via XML, enable Scrolling While Refreshing.
*/
if (!a.hasValue(R.styleable.PullToRefresh_ptrScrollingWhileRefreshingEnabled)) {
setScrollingWhileRefreshingEnabled(true);
}
}
}
@TargetApi(9)
final class InternalListViewSDK9 extends InternalListView {
public InternalListViewSDK9(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
// Does all of the hard work...
OverscrollHelper.overScrollBy(PullToRefreshListView.this, deltaX, scrollX, deltaY, scrollY, isTouchEvent);
return returnValue;
}
}
protected class InternalListView extends ListView implements EmptyViewMethodAccessor {
private boolean mAddedLvFooter = false;
public InternalListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void dispatchDraw(Canvas canvas) {
/**
* This is a bit hacky, but Samsung's ListView has got a bug in it
* when using Header/Footer Views and the list is empty. This masks
* the issue so that it doesn't cause an FC. See Issue #66.
*/
try {
super.dispatchDraw(canvas);
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
/**
* This is a bit hacky, but Samsung's ListView has got a bug in it
* when using Header/Footer Views and the list is empty. This masks
* the issue so that it doesn't cause an FC. See Issue #66.
*/
try {
return super.dispatchTouchEvent(ev);
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
return false;
}
}
@Override
public void setAdapter(ListAdapter adapter) {
// Add the Footer View at the last possible moment
if (null != mLvFooterLoadingFrame && !mAddedLvFooter) {
addFooterView(mLvFooterLoadingFrame, null, false);
mAddedLvFooter = true;
}
super.setAdapter(adapter);
}
@Override
public void setEmptyView(View emptyView) {
PullToRefreshListView.this.setEmptyView(emptyView);
}
@Override
public void setEmptyViewInternal(View emptyView) {
super.setEmptyView(emptyView);
}
}
}

View File

@ -0,0 +1,109 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ScrollView;
public class PullToRefreshScrollView extends PullToRefreshBase<ScrollView> {
public PullToRefreshScrollView(Context context) {
super(context);
}
public PullToRefreshScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PullToRefreshScrollView(Context context, Mode mode) {
super(context, mode);
}
public PullToRefreshScrollView(Context context, Mode mode, AnimationStyle style) {
super(context, mode, style);
}
@Override
public final Orientation getPullToRefreshScrollDirection() {
return Orientation.VERTICAL;
}
@Override
protected ScrollView createRefreshableView(Context context, AttributeSet attrs) {
ScrollView scrollView;
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
scrollView = new InternalScrollViewSDK9(context, attrs);
} else {
scrollView = new ScrollView(context, attrs);
}
scrollView.setId(R.id.scrollview);
return scrollView;
}
@Override
protected boolean isReadyForPullStart() {
return mRefreshableView.getScrollY() == 0;
}
@Override
protected boolean isReadyForPullEnd() {
View scrollViewChild = mRefreshableView.getChildAt(0);
if (null != scrollViewChild) {
return mRefreshableView.getScrollY() >= (scrollViewChild.getHeight() - getHeight());
}
return false;
}
@TargetApi(9)
final class InternalScrollViewSDK9 extends ScrollView {
public InternalScrollViewSDK9(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
// Does all of the hard work...
OverscrollHelper.overScrollBy(PullToRefreshScrollView.this, deltaX, scrollX, deltaY, scrollY,
getScrollRange(), isTouchEvent);
return returnValue;
}
/**
* Taken from the AOSP ScrollView source
*/
private int getScrollRange() {
int scrollRange = 0;
if (getChildCount() > 0) {
View child = getChildAt(0);
scrollRange = Math.max(0, child.getHeight() - (getHeight() - getPaddingBottom() - getPaddingTop()));
}
return scrollRange;
}
}
}

View File

@ -0,0 +1,165 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.FloatMath;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
public class PullToRefreshWebView extends PullToRefreshBase<WebView> {
private static final OnRefreshListener<WebView> defaultOnRefreshListener = new OnRefreshListener<WebView>() {
@Override
public void onRefresh(PullToRefreshBase<WebView> refreshView) {
refreshView.getRefreshableView().reload();
}
};
private final WebChromeClient defaultWebChromeClient = new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
if (newProgress == 100) {
onRefreshComplete();
}
}
};
public PullToRefreshWebView(Context context) {
super(context);
/**
* Added so that by default, Pull-to-Refresh refreshes the page
*/
setOnRefreshListener(defaultOnRefreshListener);
mRefreshableView.setWebChromeClient(defaultWebChromeClient);
}
public PullToRefreshWebView(Context context, AttributeSet attrs) {
super(context, attrs);
/**
* Added so that by default, Pull-to-Refresh refreshes the page
*/
setOnRefreshListener(defaultOnRefreshListener);
mRefreshableView.setWebChromeClient(defaultWebChromeClient);
}
public PullToRefreshWebView(Context context, Mode mode) {
super(context, mode);
/**
* Added so that by default, Pull-to-Refresh refreshes the page
*/
setOnRefreshListener(defaultOnRefreshListener);
mRefreshableView.setWebChromeClient(defaultWebChromeClient);
}
public PullToRefreshWebView(Context context, Mode mode, AnimationStyle style) {
super(context, mode, style);
/**
* Added so that by default, Pull-to-Refresh refreshes the page
*/
setOnRefreshListener(defaultOnRefreshListener);
mRefreshableView.setWebChromeClient(defaultWebChromeClient);
}
@Override
public final Orientation getPullToRefreshScrollDirection() {
return Orientation.VERTICAL;
}
@Override
protected WebView createRefreshableView(Context context, AttributeSet attrs) {
WebView webView;
if (VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
webView = new InternalWebViewSDK9(context, attrs);
} else {
webView = new WebView(context, attrs);
}
webView.setId(R.id.webview);
return webView;
}
@Override
protected boolean isReadyForPullStart() {
return mRefreshableView.getScrollY() == 0;
}
@Override
protected boolean isReadyForPullEnd() {
float exactContentHeight = FloatMath.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale());
return mRefreshableView.getScrollY() >= (exactContentHeight - mRefreshableView.getHeight());
}
@Override
protected void onPtrRestoreInstanceState(Bundle savedInstanceState) {
super.onPtrRestoreInstanceState(savedInstanceState);
mRefreshableView.restoreState(savedInstanceState);
}
@Override
protected void onPtrSaveInstanceState(Bundle saveState) {
super.onPtrSaveInstanceState(saveState);
mRefreshableView.saveState(saveState);
}
@TargetApi(9)
final class InternalWebViewSDK9 extends WebView {
// WebView doesn't always scroll back to it's edge so we add some
// fuzziness
static final int OVERSCROLL_FUZZY_THRESHOLD = 2;
// WebView seems quite reluctant to overscroll so we use the scale
// factor to scale it's value
static final float OVERSCROLL_SCALE_FACTOR = 1.5f;
public InternalWebViewSDK9(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX,
int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
final boolean returnValue = super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX,
scrollRangeY, maxOverScrollX, maxOverScrollY, isTouchEvent);
// Does all of the hard work...
OverscrollHelper.overScrollBy(PullToRefreshWebView.this, deltaX, scrollX, deltaY, scrollY,
getScrollRange(), OVERSCROLL_FUZZY_THRESHOLD, OVERSCROLL_SCALE_FACTOR, isTouchEvent);
return returnValue;
}
private int getScrollRange() {
return (int) Math.max(0, FloatMath.floor(mRefreshableView.getContentHeight() * mRefreshableView.getScale())
- (getHeight() - getPaddingBottom() - getPaddingTop()));
}
}
}

View File

@ -0,0 +1,132 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library.extras;
import java.util.concurrent.atomic.AtomicBoolean;
import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;
import com.handmark.pulltorefresh.library.PullToRefreshWebView;
/**
* An advanced version of {@link PullToRefreshWebView} which delegates the
* triggering of the PullToRefresh gesture to the Javascript running within the
* WebView. This means that you should only use this class if:
* <p/>
* <ul>
* <li>{@link PullToRefreshWebView} doesn't work correctly because you're using
* <code>overflow:scroll</code> or something else which means
* {@link WebView#getScrollY()} doesn't return correct values.</li>
* <li>You control the web content being displayed, as you need to write some
* Javascript callbacks.</li>
* </ul>
* <p/>
* <p/>
* The way this call works is that when a PullToRefresh gesture is in action,
* the following Javascript methods will be called:
* <code>isReadyForPullDown()</code> and <code>isReadyForPullUp()</code>, it is
* your job to calculate whether the view is in a state where a PullToRefresh
* can happen, and return the result via the callback mechanism. An example can
* be seen below:
* <p/>
*
* <pre>
* function isReadyForPullDown() {
* var result = ... // Probably using the .scrollTop DOM attribute
* ptr.isReadyForPullDownResponse(result);
* }
*
* function isReadyForPullUp() {
* var result = ... // Probably using the .scrollBottom DOM attribute
* ptr.isReadyForPullUpResponse(result);
* }
* </pre>
*
* @author Chris Banes
*/
public class PullToRefreshWebView2 extends PullToRefreshWebView {
static final String JS_INTERFACE_PKG = "ptr";
static final String DEF_JS_READY_PULL_DOWN_CALL = "javascript:isReadyForPullDown();";
static final String DEF_JS_READY_PULL_UP_CALL = "javascript:isReadyForPullUp();";
public PullToRefreshWebView2(Context context) {
super(context);
}
public PullToRefreshWebView2(Context context, AttributeSet attrs) {
super(context, attrs);
}
public PullToRefreshWebView2(Context context, Mode mode) {
super(context, mode);
}
private JsValueCallback mJsCallback;
private final AtomicBoolean mIsReadyForPullDown = new AtomicBoolean(false);
private final AtomicBoolean mIsReadyForPullUp = new AtomicBoolean(false);
@Override
protected WebView createRefreshableView(Context context, AttributeSet attrs) {
WebView webView = super.createRefreshableView(context, attrs);
// Need to add JS Interface so we can get the response back
mJsCallback = new JsValueCallback();
webView.addJavascriptInterface(mJsCallback, JS_INTERFACE_PKG);
return webView;
}
@Override
protected boolean isReadyForPullStart() {
// Call Javascript...
getRefreshableView().loadUrl(DEF_JS_READY_PULL_DOWN_CALL);
// Response will be given to JsValueCallback, which will update
// mIsReadyForPullDown
return mIsReadyForPullDown.get();
}
@Override
protected boolean isReadyForPullEnd() {
// Call Javascript...
getRefreshableView().loadUrl(DEF_JS_READY_PULL_UP_CALL);
// Response will be given to JsValueCallback, which will update
// mIsReadyForPullUp
return mIsReadyForPullUp.get();
}
/**
* Used for response from Javascript
*
* @author Chris Banes
*/
final class JsValueCallback {
public void isReadyForPullUpResponse(boolean response) {
mIsReadyForPullUp.set(response);
}
public void isReadyForPullDownResponse(boolean response) {
mIsReadyForPullDown.set(response);
}
}
}

View File

@ -0,0 +1,96 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library.extras;
import java.util.HashMap;
import android.content.Context;
import android.media.MediaPlayer;
import android.view.View;
import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshBase.Mode;
import com.handmark.pulltorefresh.library.PullToRefreshBase.State;
public class SoundPullEventListener<V extends View> implements PullToRefreshBase.OnPullEventListener<V> {
private final Context mContext;
private final HashMap<State, Integer> mSoundMap;
private MediaPlayer mCurrentMediaPlayer;
/**
* Constructor
*
* @param context - Context
*/
public SoundPullEventListener(Context context) {
mContext = context;
mSoundMap = new HashMap<State, Integer>();
}
@Override
public final void onPullEvent(PullToRefreshBase<V> refreshView, State event, Mode direction) {
Integer soundResIdObj = mSoundMap.get(event);
if (null != soundResIdObj) {
playSound(soundResIdObj.intValue());
}
}
/**
* Set the Sounds to be played when a Pull Event happens. You specify which
* sound plays for which events by calling this method multiple times for
* each event.
* <p/>
* If you've already set a sound for a certain event, and add another sound
* for that event, only the new sound will be played.
*
* @param event - The event for which the sound will be played.
* @param resId - Resource Id of the sound file to be played (e.g.
* <var>R.raw.pull_sound</var>)
*/
public void addSoundEvent(State event, int resId) {
mSoundMap.put(event, resId);
}
/**
* Clears all of the previously set sounds and events.
*/
public void clearSounds() {
mSoundMap.clear();
}
/**
* Gets the current (or last) MediaPlayer instance.
*/
public MediaPlayer getCurrentMediaPlayer() {
return mCurrentMediaPlayer;
}
private void playSound(int resId) {
// Stop current player, if there's one playing
if (null != mCurrentMediaPlayer) {
mCurrentMediaPlayer.stop();
mCurrentMediaPlayer.release();
}
mCurrentMediaPlayer = MediaPlayer.create(mContext, resId);
if (null != mCurrentMediaPlayer) {
mCurrentMediaPlayer.start();
}
}
}

View File

@ -0,0 +1,43 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library.internal;
import android.view.View;
/**
* Interface that allows PullToRefreshBase to hijack the call to
* AdapterView.setEmptyView()
*
* @author chris
*/
public interface EmptyViewMethodAccessor {
/**
* Calls upto AdapterView.setEmptyView()
*
* @param emptyView - to set as Empty View
*/
public void setEmptyViewInternal(View emptyView);
/**
* Should call PullToRefreshBase.setEmptyView() which will then
* automatically call through to setEmptyViewInternal()
*
* @param emptyView - to set as Empty View
*/
public void setEmptyView(View emptyView);
}

View File

@ -0,0 +1,146 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library.internal;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView.ScaleType;
import com.handmark.pulltorefresh.library.PullToRefreshBase.Mode;
import com.handmark.pulltorefresh.library.PullToRefreshBase.Orientation;
import com.handmark.pulltorefresh.library.R;
@SuppressLint("ViewConstructor")
public class FlipLoadingLayout extends LoadingLayout {
static final int FLIP_ANIMATION_DURATION = 150;
private final Animation mRotateAnimation, mResetRotateAnimation;
public FlipLoadingLayout(Context context, final Mode mode, final Orientation scrollDirection, TypedArray attrs) {
super(context, mode, scrollDirection, attrs);
final int rotateAngle = mode == Mode.PULL_FROM_START ? -180 : 180;
mRotateAnimation = new RotateAnimation(0, rotateAngle, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
mRotateAnimation.setInterpolator(ANIMATION_INTERPOLATOR);
mRotateAnimation.setDuration(FLIP_ANIMATION_DURATION);
mRotateAnimation.setFillAfter(true);
mResetRotateAnimation = new RotateAnimation(rotateAngle, 0, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
mResetRotateAnimation.setInterpolator(ANIMATION_INTERPOLATOR);
mResetRotateAnimation.setDuration(FLIP_ANIMATION_DURATION);
mResetRotateAnimation.setFillAfter(true);
}
@Override
protected void onLoadingDrawableSet(Drawable imageDrawable) {
if (null != imageDrawable) {
final int dHeight = imageDrawable.getIntrinsicHeight();
final int dWidth = imageDrawable.getIntrinsicWidth();
/**
* We need to set the width/height of the ImageView so that it is
* square with each side the size of the largest drawable dimension.
* This is so that it doesn't clip when rotated.
*/
ViewGroup.LayoutParams lp = mHeaderImage.getLayoutParams();
lp.width = lp.height = Math.max(dHeight, dWidth);
mHeaderImage.requestLayout();
/**
* We now rotate the Drawable so that is at the correct rotation,
* and is centered.
*/
mHeaderImage.setScaleType(ScaleType.MATRIX);
Matrix matrix = new Matrix();
matrix.postTranslate((lp.width - dWidth) / 2f, (lp.height - dHeight) / 2f);
matrix.postRotate(getDrawableRotationAngle(), lp.width / 2f, lp.height / 2f);
mHeaderImage.setImageMatrix(matrix);
}
}
@Override
protected void onPullImpl(float scaleOfLayout) {
// NO-OP
}
@Override
protected void pullToRefreshImpl() {
// Only start reset Animation, we've previously show the rotate anim
if (mRotateAnimation == mHeaderImage.getAnimation()) {
mHeaderImage.startAnimation(mResetRotateAnimation);
}
}
@Override
protected void refreshingImpl() {
mHeaderImage.clearAnimation();
mHeaderImage.setVisibility(View.INVISIBLE);
mHeaderProgress.setVisibility(View.VISIBLE);
}
@Override
protected void releaseToRefreshImpl() {
mHeaderImage.startAnimation(mRotateAnimation);
}
@Override
protected void resetImpl() {
mHeaderImage.clearAnimation();
mHeaderProgress.setVisibility(View.GONE);
mHeaderImage.setVisibility(View.VISIBLE);
}
@Override
protected int getDefaultDrawableResId() {
return R.drawable.default_ptr_flip;
}
private float getDrawableRotationAngle() {
float angle = 0f;
switch (mMode) {
case PULL_FROM_END:
if (mScrollDirection == Orientation.HORIZONTAL) {
angle = 90f;
} else {
angle = 180f;
}
break;
case PULL_FROM_START:
if (mScrollDirection == Orientation.HORIZONTAL) {
angle = 270f;
}
break;
default:
break;
}
return angle;
}
}

View File

@ -0,0 +1,147 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library.internal;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.R;
@SuppressLint("ViewConstructor")
public class IndicatorLayout extends FrameLayout implements AnimationListener {
static final int DEFAULT_ROTATION_ANIMATION_DURATION = 150;
private Animation mInAnim, mOutAnim;
private ImageView mArrowImageView;
private final Animation mRotateAnimation, mResetRotateAnimation;
public IndicatorLayout(Context context, PullToRefreshBase.Mode mode) {
super(context);
mArrowImageView = new ImageView(context);
Drawable arrowD = getResources().getDrawable(R.drawable.indicator_arrow);
mArrowImageView.setImageDrawable(arrowD);
final int padding = getResources().getDimensionPixelSize(R.dimen.indicator_internal_padding);
mArrowImageView.setPadding(padding, padding, padding, padding);
addView(mArrowImageView);
int inAnimResId, outAnimResId;
switch (mode) {
case PULL_FROM_END:
inAnimResId = R.anim.slide_in_from_bottom;
outAnimResId = R.anim.slide_out_to_bottom;
setBackgroundResource(R.drawable.indicator_bg_bottom);
// Rotate Arrow so it's pointing the correct way
mArrowImageView.setScaleType(ScaleType.MATRIX);
Matrix matrix = new Matrix();
matrix.setRotate(180f, arrowD.getIntrinsicWidth() / 2f, arrowD.getIntrinsicHeight() / 2f);
mArrowImageView.setImageMatrix(matrix);
break;
default:
case PULL_FROM_START:
inAnimResId = R.anim.slide_in_from_top;
outAnimResId = R.anim.slide_out_to_top;
setBackgroundResource(R.drawable.indicator_bg_top);
break;
}
mInAnim = AnimationUtils.loadAnimation(context, inAnimResId);
mInAnim.setAnimationListener(this);
mOutAnim = AnimationUtils.loadAnimation(context, outAnimResId);
mOutAnim.setAnimationListener(this);
final Interpolator interpolator = new LinearInterpolator();
mRotateAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
mRotateAnimation.setInterpolator(interpolator);
mRotateAnimation.setDuration(DEFAULT_ROTATION_ANIMATION_DURATION);
mRotateAnimation.setFillAfter(true);
mResetRotateAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
mResetRotateAnimation.setInterpolator(interpolator);
mResetRotateAnimation.setDuration(DEFAULT_ROTATION_ANIMATION_DURATION);
mResetRotateAnimation.setFillAfter(true);
}
public final boolean isVisible() {
Animation currentAnim = getAnimation();
if (null != currentAnim) {
return mInAnim == currentAnim;
}
return getVisibility() == View.VISIBLE;
}
public void hide() {
startAnimation(mOutAnim);
}
public void show() {
mArrowImageView.clearAnimation();
startAnimation(mInAnim);
}
@Override
public void onAnimationEnd(Animation animation) {
if (animation == mOutAnim) {
mArrowImageView.clearAnimation();
setVisibility(View.GONE);
} else if (animation == mInAnim) {
setVisibility(View.VISIBLE);
}
clearAnimation();
}
@Override
public void onAnimationRepeat(Animation animation) {
// NO-OP
}
@Override
public void onAnimationStart(Animation animation) {
setVisibility(View.VISIBLE);
}
public void releaseToRefresh() {
mArrowImageView.startAnimation(mRotateAnimation);
}
public void pullToRefresh() {
mArrowImageView.startAnimation(mResetRotateAnimation);
}
}

View File

@ -0,0 +1,393 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library.internal;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.handmark.pulltorefresh.library.ILoadingLayout;
import com.handmark.pulltorefresh.library.PullToRefreshBase.Mode;
import com.handmark.pulltorefresh.library.PullToRefreshBase.Orientation;
import com.handmark.pulltorefresh.library.R;
@SuppressLint("ViewConstructor")
public abstract class LoadingLayout extends FrameLayout implements ILoadingLayout {
static final String LOG_TAG = "PullToRefresh-LoadingLayout";
static final Interpolator ANIMATION_INTERPOLATOR = new LinearInterpolator();
private FrameLayout mInnerLayout;
protected final ImageView mHeaderImage;
protected final ProgressBar mHeaderProgress;
private boolean mUseIntrinsicAnimation;
private final TextView mHeaderText;
private final TextView mSubHeaderText;
protected final Mode mMode;
protected final Orientation mScrollDirection;
private CharSequence mPullLabel;
private CharSequence mRefreshingLabel;
private CharSequence mReleaseLabel;
public LoadingLayout(Context context, final Mode mode, final Orientation scrollDirection, TypedArray attrs) {
super(context);
mMode = mode;
mScrollDirection = scrollDirection;
switch (scrollDirection) {
case HORIZONTAL:
LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header_horizontal, this);
break;
case VERTICAL:
default:
LayoutInflater.from(context).inflate(R.layout.pull_to_refresh_header_vertical, this);
break;
}
mInnerLayout = (FrameLayout) findViewById(R.id.fl_inner);
mHeaderText = (TextView) mInnerLayout.findViewById(R.id.pull_to_refresh_text);
mHeaderProgress = (ProgressBar) mInnerLayout.findViewById(R.id.pull_to_refresh_progress);
mSubHeaderText = (TextView) mInnerLayout.findViewById(R.id.pull_to_refresh_sub_text);
mHeaderImage = (ImageView) mInnerLayout.findViewById(R.id.pull_to_refresh_image);
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mInnerLayout.getLayoutParams();
switch (mode) {
case PULL_FROM_END:
lp.gravity = scrollDirection == Orientation.VERTICAL ? Gravity.TOP : Gravity.LEFT;
// Load in labels
mPullLabel = context.getString(R.string.pull_to_refresh_from_bottom_pull_label);
mRefreshingLabel = context.getString(R.string.pull_to_refresh_from_bottom_refreshing_label);
mReleaseLabel = context.getString(R.string.pull_to_refresh_from_bottom_release_label);
break;
case PULL_FROM_START:
default:
lp.gravity = scrollDirection == Orientation.VERTICAL ? Gravity.BOTTOM : Gravity.RIGHT;
// Load in labels
mPullLabel = context.getString(R.string.pull_to_refresh_pull_label);
mRefreshingLabel = context.getString(R.string.pull_to_refresh_refreshing_label);
mReleaseLabel = context.getString(R.string.pull_to_refresh_release_label);
break;
}
if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderBackground)) {
Drawable background = attrs.getDrawable(R.styleable.PullToRefresh_ptrHeaderBackground);
if (null != background) {
ViewCompat.setBackground(this, background);
}
}
if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderTextAppearance)) {
TypedValue styleID = new TypedValue();
attrs.getValue(R.styleable.PullToRefresh_ptrHeaderTextAppearance, styleID);
setTextAppearance(styleID.data);
}
if (attrs.hasValue(R.styleable.PullToRefresh_ptrSubHeaderTextAppearance)) {
TypedValue styleID = new TypedValue();
attrs.getValue(R.styleable.PullToRefresh_ptrSubHeaderTextAppearance, styleID);
setSubTextAppearance(styleID.data);
}
// Text Color attrs need to be set after TextAppearance attrs
if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderTextColor)) {
ColorStateList colors = attrs.getColorStateList(R.styleable.PullToRefresh_ptrHeaderTextColor);
if (null != colors) {
setTextColor(colors);
}
}
if (attrs.hasValue(R.styleable.PullToRefresh_ptrHeaderSubTextColor)) {
ColorStateList colors = attrs.getColorStateList(R.styleable.PullToRefresh_ptrHeaderSubTextColor);
if (null != colors) {
setSubTextColor(colors);
}
}
// Try and get defined drawable from Attrs
Drawable imageDrawable = null;
if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawable)) {
imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawable);
}
// Check Specific Drawable from Attrs, these overrite the generic
// drawable attr above
switch (mode) {
case PULL_FROM_START:
default:
if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableStart)) {
imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableStart);
} else if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableTop)) {
Utils.warnDeprecation("ptrDrawableTop", "ptrDrawableStart");
imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableTop);
}
break;
case PULL_FROM_END:
if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableEnd)) {
imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableEnd);
} else if (attrs.hasValue(R.styleable.PullToRefresh_ptrDrawableBottom)) {
Utils.warnDeprecation("ptrDrawableBottom", "ptrDrawableEnd");
imageDrawable = attrs.getDrawable(R.styleable.PullToRefresh_ptrDrawableBottom);
}
break;
}
// If we don't have a user defined drawable, load the default
if (null == imageDrawable) {
imageDrawable = context.getResources().getDrawable(getDefaultDrawableResId());
}
// Set Drawable, and save width/height
setLoadingDrawable(imageDrawable);
reset();
}
public final void setHeight(int height) {
ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) getLayoutParams();
lp.height = height;
requestLayout();
}
public final void setWidth(int width) {
ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) getLayoutParams();
lp.width = width;
requestLayout();
}
public final int getContentSize() {
switch (mScrollDirection) {
case HORIZONTAL:
return mInnerLayout.getWidth();
case VERTICAL:
default:
return mInnerLayout.getHeight();
}
}
public final void hideAllViews() {
if (View.VISIBLE == mHeaderText.getVisibility()) {
mHeaderText.setVisibility(View.INVISIBLE);
}
if (View.VISIBLE == mHeaderProgress.getVisibility()) {
mHeaderProgress.setVisibility(View.INVISIBLE);
}
if (View.VISIBLE == mHeaderImage.getVisibility()) {
mHeaderImage.setVisibility(View.INVISIBLE);
}
if (View.VISIBLE == mSubHeaderText.getVisibility()) {
mSubHeaderText.setVisibility(View.INVISIBLE);
}
}
public final void onPull(float scaleOfLayout) {
if (!mUseIntrinsicAnimation) {
onPullImpl(scaleOfLayout);
}
}
public final void pullToRefresh() {
if (null != mHeaderText) {
mHeaderText.setText(mPullLabel);
}
// Now call the callback
pullToRefreshImpl();
}
public final void refreshing() {
if (null != mHeaderText) {
mHeaderText.setText(mRefreshingLabel);
}
if (mUseIntrinsicAnimation) {
((AnimationDrawable) mHeaderImage.getDrawable()).start();
} else {
// Now call the callback
refreshingImpl();
}
if (null != mSubHeaderText) {
mSubHeaderText.setVisibility(View.GONE);
}
}
public final void releaseToRefresh() {
if (null != mHeaderText) {
mHeaderText.setText(mReleaseLabel);
}
// Now call the callback
releaseToRefreshImpl();
}
public final void reset() {
if (null != mHeaderText) {
mHeaderText.setText(mPullLabel);
}
mHeaderImage.setVisibility(View.VISIBLE);
if (mUseIntrinsicAnimation) {
((AnimationDrawable) mHeaderImage.getDrawable()).stop();
} else {
// Now call the callback
resetImpl();
}
if (null != mSubHeaderText) {
if (TextUtils.isEmpty(mSubHeaderText.getText())) {
mSubHeaderText.setVisibility(View.GONE);
} else {
mSubHeaderText.setVisibility(View.VISIBLE);
}
}
}
@Override
public void setLastUpdatedLabel(CharSequence label) {
setSubHeaderText(label);
}
public final void setLoadingDrawable(Drawable imageDrawable) {
// Set Drawable
mHeaderImage.setImageDrawable(imageDrawable);
mUseIntrinsicAnimation = (imageDrawable instanceof AnimationDrawable);
// Now call the callback
onLoadingDrawableSet(imageDrawable);
}
public void setPullLabel(CharSequence pullLabel) {
mPullLabel = pullLabel;
}
public void setRefreshingLabel(CharSequence refreshingLabel) {
mRefreshingLabel = refreshingLabel;
}
public void setReleaseLabel(CharSequence releaseLabel) {
mReleaseLabel = releaseLabel;
}
@Override
public void setTextTypeface(Typeface tf) {
mHeaderText.setTypeface(tf);
}
public final void showInvisibleViews() {
if (View.INVISIBLE == mHeaderText.getVisibility()) {
mHeaderText.setVisibility(View.VISIBLE);
}
if (View.INVISIBLE == mHeaderProgress.getVisibility()) {
mHeaderProgress.setVisibility(View.VISIBLE);
}
if (View.INVISIBLE == mHeaderImage.getVisibility()) {
mHeaderImage.setVisibility(View.VISIBLE);
}
if (View.INVISIBLE == mSubHeaderText.getVisibility()) {
mSubHeaderText.setVisibility(View.VISIBLE);
}
}
/**
* Callbacks for derivative Layouts
*/
protected abstract int getDefaultDrawableResId();
protected abstract void onLoadingDrawableSet(Drawable imageDrawable);
protected abstract void onPullImpl(float scaleOfLayout);
protected abstract void pullToRefreshImpl();
protected abstract void refreshingImpl();
protected abstract void releaseToRefreshImpl();
protected abstract void resetImpl();
private void setSubHeaderText(CharSequence label) {
if (null != mSubHeaderText) {
if (TextUtils.isEmpty(label)) {
mSubHeaderText.setVisibility(View.GONE);
} else {
mSubHeaderText.setText(label);
// Only set it to Visible if we're GONE, otherwise VISIBLE will
// be set soon
if (View.GONE == mSubHeaderText.getVisibility()) {
mSubHeaderText.setVisibility(View.VISIBLE);
}
}
}
}
private void setSubTextAppearance(int value) {
if (null != mSubHeaderText) {
mSubHeaderText.setTextAppearance(getContext(), value);
}
}
private void setSubTextColor(ColorStateList color) {
if (null != mSubHeaderText) {
mSubHeaderText.setTextColor(color);
}
}
private void setTextAppearance(int value) {
if (null != mHeaderText) {
mHeaderText.setTextAppearance(getContext(), value);
}
if (null != mSubHeaderText) {
mSubHeaderText.setTextAppearance(getContext(), value);
}
}
private void setTextColor(ColorStateList color) {
if (null != mHeaderText) {
mHeaderText.setTextColor(color);
}
if (null != mSubHeaderText) {
mSubHeaderText.setTextColor(color);
}
}
}

View File

@ -0,0 +1,110 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library.internal;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Matrix;
import android.graphics.drawable.Drawable;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView.ScaleType;
import com.handmark.pulltorefresh.library.PullToRefreshBase.Mode;
import com.handmark.pulltorefresh.library.PullToRefreshBase.Orientation;
import com.handmark.pulltorefresh.library.R;
public class RotateLoadingLayout extends LoadingLayout {
static final int ROTATION_ANIMATION_DURATION = 1200;
private final Animation mRotateAnimation;
private final Matrix mHeaderImageMatrix;
private float mRotationPivotX, mRotationPivotY;
private final boolean mRotateDrawableWhilePulling;
public RotateLoadingLayout(Context context, Mode mode, Orientation scrollDirection, TypedArray attrs) {
super(context, mode, scrollDirection, attrs);
mRotateDrawableWhilePulling = attrs.getBoolean(R.styleable.PullToRefresh_ptrRotateDrawableWhilePulling, true);
mHeaderImage.setScaleType(ScaleType.MATRIX);
mHeaderImageMatrix = new Matrix();
mHeaderImage.setImageMatrix(mHeaderImageMatrix);
mRotateAnimation = new RotateAnimation(0, 720, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
0.5f);
mRotateAnimation.setInterpolator(ANIMATION_INTERPOLATOR);
mRotateAnimation.setDuration(ROTATION_ANIMATION_DURATION);
mRotateAnimation.setRepeatCount(Animation.INFINITE);
mRotateAnimation.setRepeatMode(Animation.RESTART);
}
public void onLoadingDrawableSet(Drawable imageDrawable) {
if (null != imageDrawable) {
mRotationPivotX = Math.round(imageDrawable.getIntrinsicWidth() / 2f);
mRotationPivotY = Math.round(imageDrawable.getIntrinsicHeight() / 2f);
}
}
protected void onPullImpl(float scaleOfLayout) {
float angle;
if (mRotateDrawableWhilePulling) {
angle = scaleOfLayout * 90f;
} else {
angle = Math.max(0f, Math.min(180f, scaleOfLayout * 360f - 180f));
}
mHeaderImageMatrix.setRotate(angle, mRotationPivotX, mRotationPivotY);
mHeaderImage.setImageMatrix(mHeaderImageMatrix);
}
@Override
protected void refreshingImpl() {
mHeaderImage.startAnimation(mRotateAnimation);
}
@Override
protected void resetImpl() {
mHeaderImage.clearAnimation();
resetImageRotation();
}
private void resetImageRotation() {
if (null != mHeaderImageMatrix) {
mHeaderImageMatrix.reset();
mHeaderImage.setImageMatrix(mHeaderImageMatrix);
}
}
@Override
protected void pullToRefreshImpl() {
// NO-OP
}
@Override
protected void releaseToRefreshImpl() {
// NO-OP
}
@Override
protected int getDefaultDrawableResId() {
return R.drawable.default_ptr_rotate;
}
}

View File

@ -0,0 +1,13 @@
package com.handmark.pulltorefresh.library.internal;
import android.util.Log;
public class Utils {
static final String LOG_TAG = "PullToRefresh";
public static void warnDeprecation(String depreacted, String replacement) {
Log.w(LOG_TAG, "You're using the deprecated " + depreacted + " attr, please switch over to " + replacement);
}
}

View File

@ -0,0 +1,70 @@
/*******************************************************************************
* Copyright 2011, 2012 Chris Banes.
*
* 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.
*******************************************************************************/
package com.handmark.pulltorefresh.library.internal;
import android.annotation.TargetApi;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.view.View;
@SuppressWarnings("deprecation")
public class ViewCompat {
public static void postOnAnimation(View view, Runnable runnable) {
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
SDK16.postOnAnimation(view, runnable);
} else {
view.postDelayed(runnable, 16);
}
}
public static void setBackground(View view, Drawable background) {
if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
SDK16.setBackground(view, background);
} else {
view.setBackgroundDrawable(background);
}
}
public static void setLayerType(View view, int layerType) {
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB) {
SDK11.setLayerType(view, layerType);
}
}
@TargetApi(11)
static class SDK11 {
public static void setLayerType(View view, int layerType) {
view.setLayerType(layerType, null);
}
}
@TargetApi(16)
static class SDK16 {
public static void postOnAnimation(View view, Runnable runnable) {
view.postOnAnimation(runnable);
}
public static void setBackground(View view, Drawable background) {
view.setBackground(background);
}
}
}

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2009 The Android Open Source Project
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.
-->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_longAnimTime"
android:fromYDelta="100%p"
android:toYDelta="0" />

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2009 The Android Open Source Project
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.
-->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_longAnimTime"
android:fromYDelta="-100%p"
android:toYDelta="0" />

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2009 The Android Open Source Project
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.
-->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_longAnimTime"
android:fromYDelta="0"
android:toYDelta="100%p" />

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2009 The Android Open Source Project
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.
-->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="@android:integer/config_longAnimTime"
android:fromYDelta="0"
android:toYDelta="-100%p" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 429 B

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="#40000000" />
<!--
I know the android:radius is useless here but it's needed to fix an old bug:
http://code.google.com/p/android/issues/detail?id=939
-->
<corners
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"
android:radius="1dp"
android:topLeftRadius="@dimen/indicator_corner_radius"
android:topRightRadius="@dimen/indicator_corner_radius" />
</shape>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="#40000000" />
<!--
I know the android:radius is useless here but it's needed to fix an old bug:
http://code.google.com/p/android/issues/detail?id=939
-->
<corners
android:bottomLeftRadius="@dimen/indicator_corner_radius"
android:bottomRightRadius="@dimen/indicator_corner_radius"
android:radius="1dp"
android:topLeftRadius="0dp"
android:topRightRadius="0dp" />
</shape>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<FrameLayout
android:id="@+id/fl_inner"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:paddingBottom="@dimen/header_footer_top_bottom_padding"
android:paddingLeft="@dimen/header_footer_left_right_padding"
android:paddingRight="@dimen/header_footer_left_right_padding"
android:paddingTop="@dimen/header_footer_top_bottom_padding" >
<ImageView
android:id="@+id/pull_to_refresh_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<ProgressBar
android:id="@+id/pull_to_refresh_progress"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone" />
</FrameLayout>
</merge>

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android" >
<FrameLayout
android:id="@+id/fl_inner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/header_footer_top_bottom_padding"
android:paddingLeft="@dimen/header_footer_left_right_padding"
android:paddingRight="@dimen/header_footer_left_right_padding"
android:paddingTop="@dimen/header_footer_top_bottom_padding" >
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical" >
<ImageView
android:id="@+id/pull_to_refresh_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<ProgressBar
android:id="@+id/pull_to_refresh_progress"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
android:visibility="gone" />
</FrameLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center_horizontal"
android:orientation="vertical" >
<TextView
android:id="@+id/pull_to_refresh_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearance"
android:textStyle="bold" />
<TextView
android:id="@+id/pull_to_refresh_sub_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceSmall"
android:visibility="gone" />
</LinearLayout>
</FrameLayout>
</merge>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">اسحب للتحديث…</string>
<string name="pull_to_refresh_release_label">اترك للتحديث…</string>
<string name="pull_to_refresh_refreshing_label">تحميل…</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">Tažením aktualizujete…</string>
<string name="pull_to_refresh_release_label">Uvolněním aktualizujete…</string>
<string name="pull_to_refresh_refreshing_label">Načítání…</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">Ziehen zum Aktualisieren…</string>
<string name="pull_to_refresh_release_label">Loslassen zum Aktualisieren…</string>
<string name="pull_to_refresh_refreshing_label">Laden…</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">Tirar para actualizar…</string>
<string name="pull_to_refresh_release_label">Soltar para actualizar…</string>
<string name="pull_to_refresh_refreshing_label">Cargando…</string>
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">Päivitä vetämällä alas…</string>
<string name="pull_to_refresh_release_label">Päivitä vapauttamalla…</string>
<string name="pull_to_refresh_refreshing_label">Päivitetään…</string>
<!-- Just use standard Pull Down String when pulling up. These can be set for languages which require it -->
<string name="pull_to_refresh_from_bottom_pull_label">Päivitä vetämällä ylös…</string>
<string name="pull_to_refresh_from_bottom_release_label">@string/pull_to_refresh_release_label</string>
<string name="pull_to_refresh_from_bottom_refreshing_label">@string/pull_to_refresh_refreshing_label</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">Tirez pour rafraîchir…</string>
<string name="pull_to_refresh_release_label">Relâcher pour rafraîchir…</string>
<string name="pull_to_refresh_refreshing_label">Chargement…</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">משוך לרענון…</string>
<string name="pull_to_refresh_release_label">שחרר לרענון…</string>
<string name="pull_to_refresh_refreshing_label">טוען…</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">Tira per aggiornare…</string>
<string name="pull_to_refresh_release_label">Rilascia per aggionare…</string>
<string name="pull_to_refresh_refreshing_label">Caricamento…</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">משוך לרענון…</string>
<string name="pull_to_refresh_release_label">שחרר לרענון…</string>
<string name="pull_to_refresh_refreshing_label">טוען…</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">画面を引っ張って…</string>
<string name="pull_to_refresh_release_label">指を離して更新…</string>
<string name="pull_to_refresh_refreshing_label">読み込み中…</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">당겨서 새로 고침…</string>
<string name="pull_to_refresh_release_label">놓아서 새로 고침…</string>
<string name="pull_to_refresh_refreshing_label">로드 중…</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">Sleep om te vernieuwen…</string>
<string name="pull_to_refresh_release_label">Loslaten om te vernieuwen…</string>
<string name="pull_to_refresh_refreshing_label">Laden…</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">Pociągnij, aby odświeżyć…</string>
<string name="pull_to_refresh_release_label">Puść, aby odświeżyć…</string>
<string name="pull_to_refresh_refreshing_label">Wczytywanie…</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">Puxe para atualizar…</string>
<string name="pull_to_refresh_release_label">Libere para atualizar…</string>
<string name="pull_to_refresh_refreshing_label">Carregando…</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">Puxe para atualizar…</string>
<string name="pull_to_refresh_release_label">Liberação para atualizar…</string>
<string name="pull_to_refresh_refreshing_label">A carregar…</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">Trage pentru a reîmprospăta…</string>
<string name="pull_to_refresh_release_label">Eliberează pentru a reîmprospăta…</string>
<string name="pull_to_refresh_refreshing_label">Încărcare…</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">Потяните для обновления…</string>
<string name="pull_to_refresh_release_label">Отпустите для обновления…</string>
<string name="pull_to_refresh_refreshing_label">Загрузка…</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">下拉刷新…</string>
<string name="pull_to_refresh_release_label">放开以刷新…</string>
<string name="pull_to_refresh_refreshing_label">正在载入…</string>
</resources>

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="PullToRefresh">
<!-- A drawable to use as the background of the Refreshable View -->
<attr name="ptrRefreshableViewBackground" format="reference|color" />
<!-- A drawable to use as the background of the Header and Footer Loading Views -->
<attr name="ptrHeaderBackground" format="reference|color" />
<!-- Text Color of the Header and Footer Loading Views -->
<attr name="ptrHeaderTextColor" format="reference|color" />
<!-- Text Color of the Header and Footer Loading Views Sub Header -->
<attr name="ptrHeaderSubTextColor" format="reference|color" />
<!-- Mode of Pull-to-Refresh that should be used -->
<attr name="ptrMode">
<flag name="disabled" value="0x0" />
<flag name="pullFromStart" value="0x1" />
<flag name="pullFromEnd" value="0x2" />
<flag name="both" value="0x3" />
<flag name="manualOnly" value="0x4" />
<!-- These last two are depreacted -->
<flag name="pullDownFromTop" value="0x1" />
<flag name="pullUpFromBottom" value="0x2" />
</attr>
<!-- Whether the Indicator overlay(s) should be used -->
<attr name="ptrShowIndicator" format="reference|boolean" />
<!-- Drawable to use as Loading Indicator. Changes both Header and Footer. -->
<attr name="ptrDrawable" format="reference" />
<!-- Drawable to use as Loading Indicator in the Header View. Overrides value set in ptrDrawable. -->
<attr name="ptrDrawableStart" format="reference" />
<!-- Drawable to use as Loading Indicator in the Footer View. Overrides value set in ptrDrawable. -->
<attr name="ptrDrawableEnd" format="reference" />
<!-- Whether Android's built-in Over Scroll should be utilised for Pull-to-Refresh. -->
<attr name="ptrOverScroll" format="reference|boolean" />
<!-- Base text color, typeface, size, and style for Header and Footer Loading Views -->
<attr name="ptrHeaderTextAppearance" format="reference" />
<!-- Base text color, typeface, size, and style for Header and Footer Loading Views Sub Header -->
<attr name="ptrSubHeaderTextAppearance" format="reference" />
<!-- Style of Animation should be used displayed when pulling. -->
<attr name="ptrAnimationStyle">
<flag name="rotate" value="0x0" />
<flag name="flip" value="0x1" />
</attr>
<!-- Whether the user can scroll while the View is Refreshing -->
<attr name="ptrScrollingWhileRefreshingEnabled" format="reference|boolean" />
<!--
Whether PullToRefreshListView has it's extras enabled. This allows the user to be
able to scroll while refreshing, and behaves better. It acheives this by adding
Header and/or Footer Views to the ListView.
-->
<attr name="ptrListViewExtrasEnabled" format="reference|boolean" />
<!--
Whether the Drawable should be continually rotated as you pull. This only
takes effect when using the 'Rotate' Animation Style.
-->
<attr name="ptrRotateDrawableWhilePulling" format="reference|boolean" />
<!-- BELOW HERE ARE DEPRECEATED. DO NOT USE. -->
<attr name="ptrAdapterViewBackground" format="reference|color" />
<attr name="ptrDrawableTop" format="reference" />
<attr name="ptrDrawableBottom" format="reference" />
</declare-styleable>
</resources>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="indicator_right_padding">10dp</dimen>
<dimen name="indicator_corner_radius">12dp</dimen>
<dimen name="indicator_internal_padding">4dp</dimen>
<dimen name="header_footer_left_right_padding">24dp</dimen>
<dimen name="header_footer_top_bottom_padding">12dp</dimen>
</resources>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="gridview" />
<item type="id" name="webview" />
<item type="id" name="scrollview" />
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pull_to_refresh_pull_label">Pull to refresh…</string>
<string name="pull_to_refresh_release_label">Release to refresh…</string>
<string name="pull_to_refresh_refreshing_label">Loading…</string>
<!-- Just use standard Pull Down String when pulling up. These can be set for languages which require it -->
<string name="pull_to_refresh_from_bottom_pull_label">@string/pull_to_refresh_pull_label</string>
<string name="pull_to_refresh_from_bottom_release_label">@string/pull_to_refresh_release_label</string>
<string name="pull_to_refresh_from_bottom_refreshing_label">@string/pull_to_refresh_refreshing_label</string>
</resources>