Missing submodule files added
18
menudrawer/build.gradle
Normal 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
@ -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>
|
8
menudrawer/src/main/AndroidManifest.xml
Normal 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>
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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><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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
1652
menudrawer/src/main/java/net/simonvt/menudrawer/MenuDrawer.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
505
menudrawer/src/main/java/net/simonvt/menudrawer/Scroller.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
65
menudrawer/src/main/res/values/attrs.xml
Normal 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>
|
6
menudrawer/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
|
||||
<!-- The default background of the menu. -->
|
||||
<color name="md__defaultBackground">#FF555555</color>
|
||||
|
||||
</resources>
|
24
menudrawer/src/main/res/values/ids.xml
Normal 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>
|
8
menudrawer/src/main/res/values/strings.xml
Normal 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>
|
13
menudrawer/src/main/res/values/styles.xml
Normal 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>
|
18
pulltorefresh/build.gradle
Normal 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'
|
||||
}
|
||||
}
|
||||
}
|
100
pulltorefresh/pulltorefresh.iml
Normal 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>
|
8
pulltorefresh/src/main/AndroidManifest.xml
Normal 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>
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
21
pulltorefresh/src/main/res/anim/slide_in_from_bottom.xml
Normal 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" />
|
21
pulltorefresh/src/main/res/anim/slide_in_from_top.xml
Normal 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" />
|
21
pulltorefresh/src/main/res/anim/slide_out_to_bottom.xml
Normal 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" />
|
21
pulltorefresh/src/main/res/anim/slide_out_to_top.xml
Normal 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" />
|
BIN
pulltorefresh/src/main/res/drawable-hdpi/default_ptr_flip.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
pulltorefresh/src/main/res/drawable-hdpi/default_ptr_rotate.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
pulltorefresh/src/main/res/drawable-hdpi/indicator_arrow.png
Normal file
After Width: | Height: | Size: 390 B |
BIN
pulltorefresh/src/main/res/drawable-mdpi/default_ptr_flip.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
pulltorefresh/src/main/res/drawable-mdpi/default_ptr_rotate.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
pulltorefresh/src/main/res/drawable-mdpi/indicator_arrow.png
Normal file
After Width: | Height: | Size: 445 B |
BIN
pulltorefresh/src/main/res/drawable-xhdpi/default_ptr_flip.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
pulltorefresh/src/main/res/drawable-xhdpi/default_ptr_rotate.png
Normal file
After Width: | Height: | Size: 49 KiB |
BIN
pulltorefresh/src/main/res/drawable-xhdpi/indicator_arrow.png
Normal file
After Width: | Height: | Size: 429 B |
18
pulltorefresh/src/main/res/drawable/indicator_bg_bottom.xml
Normal 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>
|
18
pulltorefresh/src/main/res/drawable/indicator_bg_top.xml
Normal 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>
|
@ -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>
|
@ -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>
|
@ -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>
|
6
pulltorefresh/src/main/res/values-cs/pull_refresh_strings.xml
Executable 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>
|
6
pulltorefresh/src/main/res/values-de/pull_refresh_strings.xml
Executable 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>
|
6
pulltorefresh/src/main/res/values-es/pull_refresh_strings.xml
Executable 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>
|
13
pulltorefresh/src/main/res/values-fi/pull_refresh_strings.xml
Executable 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>
|
6
pulltorefresh/src/main/res/values-fr/pull_refresh_strings.xml
Executable 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>
|
@ -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>
|
6
pulltorefresh/src/main/res/values-it/pull_refresh_strings.xml
Executable 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>
|
@ -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>
|
@ -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>
|
6
pulltorefresh/src/main/res/values-ko/pull_refresh_strings.xml
Executable 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>
|
6
pulltorefresh/src/main/res/values-nl/pull_refresh_strings.xml
Executable 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>
|
6
pulltorefresh/src/main/res/values-pl/pull_refresh_strings.xml
Executable 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>
|
6
pulltorefresh/src/main/res/values-pt-rBR/pull_refresh_strings.xml
Executable 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>
|
6
pulltorefresh/src/main/res/values-pt/pull_refresh_strings.xml
Executable 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>
|
@ -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>
|
6
pulltorefresh/src/main/res/values-ru/pull_refresh_strings.xml
Executable 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>
|
6
pulltorefresh/src/main/res/values-zh/pull_refresh_strings.xml
Executable 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>
|
80
pulltorefresh/src/main/res/values/attrs.xml
Normal 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>
|
10
pulltorefresh/src/main/res/values/dimens.xml
Normal 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>
|
8
pulltorefresh/src/main/res/values/ids.xml
Normal 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>
|
13
pulltorefresh/src/main/res/values/pull_refresh_strings.xml
Executable 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>
|