diff --git a/build.gradle b/build.gradle index 6588cd8..eea0f7e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,23 @@ buildscript { repositories { mavenCentral() + google() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:3.0.1' } } + +allprojects { + repositories { + mavenCentral() + google() + } +} + +ext { + androidBuildTool = '27.0.1' + androidTargetSdk = 27 + androidSupportLib = '27.0.1' + androidMinSdk = 14 +} \ No newline at end of file diff --git a/drawer-behavior/build.gradle b/drawer-behavior/build.gradle index 73f791b..eb46eb8 100644 --- a/drawer-behavior/build.gradle +++ b/drawer-behavior/build.gradle @@ -1,14 +1,15 @@ apply plugin: 'com.android.library' android { - compileSdkVersion 23 - buildToolsVersion '23.0.3' + compileSdkVersion androidTargetSdk + buildToolsVersion androidBuildTool defaultConfig { - minSdkVersion 7 + minSdkVersion androidMinSdk } + } dependencies { - compile 'com.android.support:design:23.4.0' + implementation 'com.android.support:design:' + androidSupportLib } diff --git a/drawer-behavior/src/main/java/com/jakewharton/behavior/drawer/BehaviorDelegate.java b/drawer-behavior/src/main/java/com/jakewharton/behavior/drawer/BehaviorDelegate.java index 44a736f..56fd9d3 100644 --- a/drawer-behavior/src/main/java/com/jakewharton/behavior/drawer/BehaviorDelegate.java +++ b/drawer-behavior/src/main/java/com/jakewharton/behavior/drawer/BehaviorDelegate.java @@ -18,11 +18,13 @@ import android.os.Build; import android.os.SystemClock; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.design.widget.CoordinatorLayout; import android.support.v4.view.GravityCompat; -import android.support.v4.view.MotionEventCompat; import android.support.v4.view.ViewCompat; import android.support.v4.widget.ViewDragHelper; +import android.util.Log; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; @@ -30,13 +32,19 @@ import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; +import static com.jakewharton.behavior.drawer.DrawerBehavior.DrawerListener; +import static com.jakewharton.behavior.drawer.DrawerBehavior.State; final class BehaviorDelegate extends ViewDragHelper.Callback { + private static final boolean DEBUG = true; private static final int PEEK_DELAY = 160; // ms private static final int MIN_FLING_VELOCITY = 400; // dips per second - private static final int FLAG_IS_OPENED = 0x1; - private static final int FLAG_IS_OPENING = 0x2; - private static final int FLAG_IS_CLOSING = 0x4; + static final int FLAG_IS_OPENED = 0x1; + @SuppressWarnings("WeakerAccess") + static final int FLAG_IS_OPENING = 0x2; + @SuppressWarnings("unused") + static final int FLAG_IS_CLOSING = 0x4; + static final int FLAG_IS_CLOSED = 0x0; private static final int DEFAULT_SCRIM_COLOR = 0x99000000; private final CoordinatorLayout parent; @@ -45,14 +53,19 @@ final class BehaviorDelegate extends ViewDragHelper.Callback { private final ContentScrimDrawer scrimDrawer; private final ViewDragHelper dragger; + private DrawerListener listener; + private float initialMotionX; private float initialMotionY; private boolean childrenCanceledTouch; private int openState; private boolean isPeeking; private float onScreen; + + @State private int drawerState; + @SuppressWarnings("FieldCanBeLocal") private int scrimColor = DEFAULT_SCRIM_COLOR; private final Runnable peekRunnable = new Runnable() { @@ -134,10 +147,22 @@ private void cancelChildViewTouch() { } } + boolean isDrawerOpen() { + return openState == FLAG_IS_OPENED || openState == FLAG_IS_OPENING; + } + + void setDrawerState(int openState) { + this.openState = openState; + } + + void setDrawListener(DrawerListener listener) { + this.listener = listener; + } + boolean onInterceptTouchEvent(MotionEvent ev) { boolean interceptForDrag = dragger.shouldInterceptTouchEvent(ev); boolean interceptForTap = false; - switch (MotionEventCompat.getActionMasked(ev)) { + switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: { float x = ev.getX(); float y = ev.getY(); @@ -175,7 +200,7 @@ boolean onInterceptTouchEvent(MotionEvent ev) { boolean onTouchEvent(MotionEvent ev) { dragger.processTouchEvent(ev); - switch (MotionEventCompat.getActionMasked(ev)) { + switch (ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: { initialMotionX = ev.getX(); initialMotionY = ev.getY(); @@ -194,7 +219,7 @@ boolean onTouchEvent(MotionEvent ev) { final int slop = dragger.getTouchSlop(); if (dx * dx + dy * dy < slop * slop) { // Taps close a dimmed open drawer but only if it isn't locked open. - if ((openState & FLAG_IS_OPENED) == FLAG_IS_OPENED) { // TODO isDrawerOpen method? + if ((openState & FLAG_IS_OPENED) == FLAG_IS_OPENED) { peekingOnly = false; } } @@ -218,6 +243,8 @@ private void closeDrawers(boolean peekingOnly) { return; } + removeCallbacks(); + boolean needsSettle; if (isLeft) { needsSettle = dragger.smoothSlideViewTo(child, -child.getWidth(), child.getTop()); @@ -226,18 +253,16 @@ private void closeDrawers(boolean peekingOnly) { } isPeeking = false; - removeCallbacks(); - if (needsSettle) { ViewCompat.postOnAnimation(parent, draggerSettle); } } - @Override public void onViewCaptured(View capturedChild, int activePointerId) { + @Override public void onViewCaptured(@NonNull View capturedChild, int activePointerId) { isPeeking = false; } - @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { + @Override public void onViewReleased(@NonNull View releasedChild, float xvel, float yvel) { // Offset is how open the drawer is, therefore left/right values // are reversed from one another. float offset = onScreen; @@ -259,9 +284,17 @@ private void closeDrawers(boolean peekingOnly) { updateDrawerState(state, dragger.getCapturedView()); } - private void updateDrawerState(int activeState, View activeDrawer) { - final int state = dragger.getViewDragState(); - + private void updateDrawerState(int activeState,@Nullable View activeDrawer) { + @State final int state = dragger.getViewDragState(); + if (DEBUG) { + if (activeDrawer != null) + Log.d("updateDrawerState","activeState: " + + activeState + "activeDrawer position: " + + activeDrawer.getLeft() + ":" + activeDrawer.getRight()); + else + Log.d("updateDrawerState", "activeState: " + + "activeDrawer is null"); + } if (activeDrawer != null && activeState == ViewDragHelper.STATE_IDLE) { if (onScreen == 0) { dispatchOnDrawerClosed(activeDrawer); @@ -273,6 +306,8 @@ private void updateDrawerState(int activeState, View activeDrawer) { if (state != drawerState) { drawerState = state; } + if (listener != null) + listener.onDrawerStateChanged(activeDrawer,drawerState); } private void dispatchOnDrawerClosed(View drawerView) { @@ -290,6 +325,8 @@ private void dispatchOnDrawerClosed(View drawerView) { rootView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } } + if (listener != null) + listener.onDrawerClosed(drawerView); } } @@ -305,6 +342,8 @@ private void dispatchOnDrawerOpened(View drawerView) { } drawerView.requestFocus(); + if (listener != null) + listener.onDrawerOpened(drawerView); } } @@ -326,9 +365,9 @@ private void updateChildrenImportantForAccessibility(View drawerView, boolean is } @Override - public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { + public void onViewPositionChanged(@NonNull View changedView, int left, int top, int dx, int dy) { int childWidth = changedView.getWidth(); - + Log.d("onViewPositionChanged",left +":" +top+ ":"+dx+":"+dy); // This reverses the positioning shown in onLayout. float offset; if (isLeft) { @@ -346,26 +385,28 @@ public void onViewPositionChanged(View changedView, int left, int top, int dx, i int color = imag << 24 | (scrimColor & 0xffffff); scrimDrawer.setColor(color); - setDrawerViewOffset(offset); + setDrawerViewOffset(changedView,offset); boolean gone = offset == 0; changedView.setVisibility(gone ? INVISIBLE : VISIBLE); scrimDrawer.setVisible(!gone); parent.invalidate(); } - private void setDrawerViewOffset(float slideOffset) { + private void setDrawerViewOffset(View drawerView,float slideOffset) { if (slideOffset == onScreen) { return; } onScreen = slideOffset; + if (listener != null) + listener.onDrawerSlide(drawerView,slideOffset); } @Override public void onEdgeTouched(int edgeFlags, int pointerId) { parent.postDelayed(peekRunnable, PEEK_DELAY); } - @Override public boolean tryCaptureView(View child, int pointerId) { + @Override public boolean tryCaptureView(@NonNull View child, int pointerId) { return isDrawerView(child); } @@ -376,11 +417,11 @@ private void setDrawerViewOffset(float slideOffset) { } } - @Override public int getViewHorizontalDragRange(View child) { + @Override public int getViewHorizontalDragRange(@NonNull View child) { return isDrawerView(child) ? child.getWidth() : 0; } - @Override public int clampViewPositionHorizontal(View child, int left, int dx) { + @Override public int clampViewPositionHorizontal(@NonNull View child, int left, int dx) { if (isLeft) { return Math.max(-child.getWidth(), Math.min(left, 0)); } else { @@ -389,7 +430,8 @@ private void setDrawerViewOffset(float slideOffset) { } } - @Override public int clampViewPositionVertical(View child, int top, int dy) { + @Override public int clampViewPositionVertical(@NonNull View child, int top, int dy) { + Log.d("ViewPositionVertical", "" + child.getTop()); return child.getTop(); } @@ -447,7 +489,7 @@ boolean onLayoutChild() { } if (changeOffset) { - setDrawerViewOffset(newOffset); + setDrawerViewOffset(child,newOffset); } int newVisibility = onScreen > 0 ? VISIBLE : INVISIBLE; diff --git a/drawer-behavior/src/main/java/com/jakewharton/behavior/drawer/DrawerBehavior.java b/drawer-behavior/src/main/java/com/jakewharton/behavior/drawer/DrawerBehavior.java index f228d60..36d0d76 100644 --- a/drawer-behavior/src/main/java/com/jakewharton/behavior/drawer/DrawerBehavior.java +++ b/drawer-behavior/src/main/java/com/jakewharton/behavior/drawer/DrawerBehavior.java @@ -17,16 +17,71 @@ import android.content.Context; import android.content.res.TypedArray; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.IntDef; import android.support.annotation.Keep; +import android.support.annotation.RestrictTo; import android.support.design.widget.CoordinatorLayout; import android.support.v4.util.SimpleArrayMap; +import android.support.v4.view.AbsSavedState; import android.support.v4.view.GravityCompat; +import android.support.v4.widget.ViewDragHelper; import android.util.AttributeSet; import android.view.Gravity; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; public final class DrawerBehavior extends CoordinatorLayout.Behavior { + + @SuppressWarnings("WeakerAccess") + @RestrictTo(LIBRARY_GROUP) + @IntDef({ViewDragHelper.STATE_IDLE, ViewDragHelper.STATE_DRAGGING, ViewDragHelper.STATE_SETTLING}) + @Retention(RetentionPolicy.SOURCE) + public @interface State {} + + /** + * Listener for monitoring events about drawers. + */ + public interface DrawerListener { + /** + * Called when a drawer's position changes. + * @param drawerView The child view that was moved + * @param slideOffset The new offset of this drawer within its range, from 0-1 + */ + void onDrawerSlide(View drawerView, float slideOffset); + + /** + * Called when a drawer has settled in a completely open state. + * The drawer is interactive at this point. + * + * @param drawerView Drawer view that is now open + */ + void onDrawerOpened(View drawerView); + + /** + * Called when a drawer has settled in a completely closed state. + * + * @param drawerView Drawer view that is now closed + */ + void onDrawerClosed(View drawerView); + + /** + * Called when the drawer motion state changes. The new state will + * + * be one of {@link ViewDragHelper#STATE_IDLE}, {@link ViewDragHelper#STATE_DRAGGING} or {@link ViewDragHelper#STATE_SETTLING}. + * @param drawerView Drawer view which motion state is changed + * @param newState The new drawer motion state + */ + void onDrawerStateChanged(View drawerView,@State int newState); + } + private static void validateGravity(int gravity) { if (gravity != Gravity.LEFT && gravity != Gravity.RIGHT @@ -37,8 +92,11 @@ private static void validateGravity(int gravity) { } private final SimpleArrayMap delegates = new SimpleArrayMap<>(); + private final int gravity; + private DrawerListener listener; + @SuppressWarnings("unused") // Public API for programmatic instantiation. public DrawerBehavior(int gravity) { validateGravity(gravity); @@ -57,10 +115,15 @@ public DrawerBehavior(Context context, AttributeSet attrs) { this.gravity = gravity; } + public void setDrawerListener(DrawerListener listener) { + this.listener = listener; + } + private BehaviorDelegate delegate(CoordinatorLayout parent, View child) { BehaviorDelegate delegate = delegates.get(child); if (delegate == null) { delegate = new BehaviorDelegate(parent, child, gravity); + delegate.setDrawListener(listener); delegates.put(child, delegate); } return delegate; @@ -80,4 +143,76 @@ public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, Motio @Override public boolean onTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) { return delegate(parent, child).onTouchEvent(ev); } + + @Override + public Parcelable onSaveInstanceState(CoordinatorLayout parent, View child) { + return new SavedState(super.onSaveInstanceState(parent, child),delegates.get(child).isDrawerOpen()) ; + } + + @Override + public void onRestoreInstanceState(CoordinatorLayout parent, View child, Parcelable state) { + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(parent, child, ss.getSuperState()); + delegates.get(child).setDrawerState(ss.openState?BehaviorDelegate.FLAG_IS_OPENED:BehaviorDelegate.FLAG_IS_CLOSED); + } + + private static class SavedState extends AbsSavedState { + + boolean openState; + + private SavedState(Parcelable superState,boolean openState) { + super(superState); + this.openState = openState; + } + + private SavedState(Parcel source) { + this(source,null); + } + + private SavedState(Parcel source, ClassLoader loader) { + super(source, loader); + openState = source.readInt() != 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(openState?1:0); + } + + public static final Creator CREATOR = new ClassLoaderCreator() { + @Override + public SavedState createFromParcel(Parcel parcel, ClassLoader classLoader) { + return new SavedState(parcel, classLoader); + } + + @Override + public SavedState createFromParcel(Parcel parcel) { + return new SavedState(parcel); + } + + @Override + public SavedState[] newArray(int i) { + return new SavedState[i]; + } + }; + } + + /** + * todo + * */ + public static DrawerBehavior from(View view) { + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + if (!(layoutParams instanceof CoordinatorLayout.LayoutParams)) { + throw new IllegalArgumentException("The view is not a child of CoordinatorLayout"); + } + CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) layoutParams; + CoordinatorLayout.Behavior behavior = lp.getBehavior(); + if (!(behavior instanceof DrawerBehavior)) { + throw new IllegalArgumentException( + "The view is not associated with DrawerBehavior"); + } + return (DrawerBehavior) behavior; + } + } diff --git a/drawer-behavior/src/main/res/values/strings.xml b/drawer-behavior/src/main/res/values/strings.xml new file mode 100644 index 0000000..e02e8f7 --- /dev/null +++ b/drawer-behavior/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + com.jakewharton.behavior.drawer.DrawerBehavior + \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ab100c0..fc9b829 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.13-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip diff --git a/sample/build.gradle b/sample/build.gradle index fe4ff63..a9547be 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -1,18 +1,20 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 23 - buildToolsVersion '23.0.3' + compileSdkVersion androidTargetSdk + buildToolsVersion androidBuildTool defaultConfig { applicationId 'com.example.behavior.drawer' - minSdkVersion 7 - targetSdkVersion 23 + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk versionCode 1 versionName '1.0' } } dependencies { - compile project(':drawer-behavior') + implementation project(':drawer-behavior') + implementation "com.android.support:appcompat-v7:27.0.1" + implementation 'com.android.support:design:' + androidSupportLib } diff --git a/sample/src/main/java/com/example/behavior/drawer/DrawerBehaviorActivity.java b/sample/src/main/java/com/example/behavior/drawer/DrawerBehaviorActivity.java index 3283d43..83ee4eb 100644 --- a/sample/src/main/java/com/example/behavior/drawer/DrawerBehaviorActivity.java +++ b/sample/src/main/java/com/example/behavior/drawer/DrawerBehaviorActivity.java @@ -2,10 +2,41 @@ import android.support.v7.app.AppCompatActivity; import android.os.Bundle; +import android.util.Log; +import android.view.View; + +import com.jakewharton.behavior.drawer.DrawerBehavior; public final class DrawerBehaviorActivity extends AppCompatActivity { + private static final String TAG = DrawerBehaviorActivity.class.getCanonicalName(); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.drawer_behavior); + View view = findViewById(R.id.view); + DrawerBehavior behavior = DrawerBehavior.from(view); + behavior.setDrawerListener(new DrawerBehavior.DrawerListener() { + @Override + public void onDrawerSlide(View drawerView, float slideOffset) { + Log.d(TAG, "onDrawerSlide: drawerView: " + + drawerView.toString() + + " slideOffset: " + slideOffset); + } + + @Override + public void onDrawerOpened(View drawerView) { + Log.d(TAG,"opened:"+ drawerView.toString()); + } + + @Override + public void onDrawerClosed(View drawerView) { + Log.d(TAG,"closed:"+ drawerView.toString()); + } + + @Override + public void onDrawerStateChanged(View drawerView, int newState) { + Log.d(TAG,"onDrawerStateChanged:"+ newState); + } + }); } } diff --git a/sample/src/main/res/layout/drawer_behavior.xml b/sample/src/main/res/layout/drawer_behavior.xml index c8cb49c..39f8b9f 100644 --- a/sample/src/main/res/layout/drawer_behavior.xml +++ b/sample/src/main/res/layout/drawer_behavior.xml @@ -7,10 +7,11 @@ android:background="#feee" >