From 82a98786f301cd8e7fcafc44bf760f0434bd2eb2 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Wed, 5 Jul 2017 21:17:03 -0700 Subject: Bring back swipe --- .../ca/allanwang/kau/swipe/RelativeSlider.kt | 4 + .../ca/allanwang/kau/swipe/SwipeBackHelper.kt | 31 ++-- .../ca/allanwang/kau/swipe/SwipeBackLayout.kt | 160 ++++++++++----------- .../kotlin/ca/allanwang/kau/swipe/SwipeBackPage.kt | 6 +- .../kotlin/ca/allanwang/kau/swipe/SwipeListener.kt | 9 +- .../ca/allanwang/kau/swipe/ViewDragHelper.java | 2 +- .../allanwang/kau/swipe/ViewDragHelperExtras.java | 3 +- 7 files changed, 111 insertions(+), 104 deletions(-) (limited to 'core/src/main/kotlin') diff --git a/core/src/main/kotlin/ca/allanwang/kau/swipe/RelativeSlider.kt b/core/src/main/kotlin/ca/allanwang/kau/swipe/RelativeSlider.kt index 2bbe9b0..2ffb2ef 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/swipe/RelativeSlider.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/swipe/RelativeSlider.kt @@ -4,6 +4,10 @@ import ca.allanwang.kau.kotlin.nonReadable /** * Created by Mr.Jude on 2015/8/26. + * + * Updated by Allan Wang on 2017/07/05 + * + * Helper class to give the previous activity an offset as the main page is pulled */ class RelativeSlider(var curPage: SwipeBackPage) : SwipeListener { diff --git a/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackHelper.kt b/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackHelper.kt index 92cdd7c..b24c7d8 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackHelper.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackHelper.kt @@ -2,11 +2,16 @@ package ca.allanwang.kau.swipe import android.app.Activity import ca.allanwang.kau.R -import ca.allanwang.kau.logging.KL +import ca.allanwang.kau.swipe.SwipeBackHelper.onDestroy import java.util.* -class SwipeBackException(message: String = "You Should call SwipeBackHelper.onCreate(activity) first") : RuntimeException(message) +class SwipeBackException(message: String = "You Should call kauSwipeOnCreate() first") : RuntimeException(message) +/** + * Singleton to hold our swipe stack + * All activity pages held with strong references, so it is crucial to call + * [onDestroy] whenever an activity should be disposed + */ object SwipeBackHelper { private val pageStack = Stack() @@ -16,7 +21,7 @@ object SwipeBackHelper { fun getCurrentPage(activity: Activity): SwipeBackPage = this[activity] - fun onCreate(activity: Activity, builder: SwipeBackPage.() -> Unit = {}) { + fun onCreate(activity: Activity, builder: SwipeBackContract.() -> Unit = {}) { val page = pageStack.firstOrNull { it.activity === activity } ?: pageStack.push(SwipeBackPage(activity).apply { builder() }) val startAnimation: Int = with(page.edgeFlag) { if (this and SWIPE_EDGE_LEFT > 0) R.anim.kau_slide_in_right @@ -30,7 +35,6 @@ object SwipeBackHelper { fun onPostCreate(activity: Activity) = this[activity].onPostCreate() fun onDestroy(activity: Activity) { - KL.d("Swipe destroy") val page: SwipeBackPage = this[activity] pageStack.remove(page) page.activity = null @@ -45,21 +49,24 @@ object SwipeBackHelper { } -fun Activity.kauSwipeOnCreate(builder: SwipeBackPage.() -> Unit = {}) = SwipeBackHelper.onCreate(this, builder) +/** + * The following are the activity bindings to add an activity to the stack + * onCreate, onPostCreate, and onDestroy are mandatory + * finish is there as a helper method to animate the transaction + */ +fun Activity.kauSwipeOnCreate(builder: SwipeBackContract.() -> Unit = {}) = SwipeBackHelper.onCreate(this, builder) + fun Activity.kauSwipeOnPostCreate() = SwipeBackHelper.onPostCreate(this) fun Activity.kauSwipeOnDestroy() = SwipeBackHelper.onDestroy(this) fun Activity.kauSwipeFinish() = SwipeBackHelper.finish(this) +/** + * Constants used for the swipe edge flags + */ const val SWIPE_EDGE_LEFT = ViewDragHelper.EDGE_LEFT const val SWIPE_EDGE_RIGHT = ViewDragHelper.EDGE_RIGHT const val SWIPE_EDGE_TOP = ViewDragHelper.EDGE_TOP -const val SWIPE_EDGE_BOTTOM = ViewDragHelper.EDGE_BOTTOM - -const val SWIPE_EDGE_HORIZONTAL = SWIPE_EDGE_LEFT or SWIPE_EDGE_RIGHT - -const val SWIPE_EDGE_VERTICAL = SWIPE_EDGE_TOP or SWIPE_EDGE_BOTTOM - -const val SWIPE_EDGE_ALL = SWIPE_EDGE_HORIZONTAL or SWIPE_EDGE_VERTICAL \ No newline at end of file +const val SWIPE_EDGE_BOTTOM = ViewDragHelper.EDGE_BOTTOM \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackLayout.kt b/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackLayout.kt index 9d5fc72..22de709 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackLayout.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackLayout.kt @@ -3,7 +3,6 @@ package ca.allanwang.kau.swipe import android.app.Activity import android.content.Context import android.graphics.Canvas -import android.graphics.Color import android.support.v4.view.ViewCompat import android.util.AttributeSet import android.view.MotionEvent @@ -11,11 +10,18 @@ import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import ca.allanwang.kau.logging.KL +import ca.allanwang.kau.utils.adjustAlpha import ca.allanwang.kau.utils.navigationBarColor import ca.allanwang.kau.utils.statusBarColor -import ca.allanwang.kau.utils.withAlpha import java.lang.ref.WeakReference +/** + * The layout that handles all the touch events + * Note that this differs from [ca.allanwang.kau.widgets.ElasticDragDismissFrameLayout] + * in that nested scrolling isn't considered + * If an edge detection occurs, this layout consumes all the touch events + * Use the [swipeEnabled] toggle if you need the scroll events on the same axis + */ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : FrameLayout(context, attrs, defStyle), SwipeBackContract { @@ -35,8 +41,8 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu set(value) { field = value if (value != null) { - statusBarAlpha = Color.alpha(value.statusBarColor) - navBarAlpha = Color.alpha(value.navigationBarColor) + statusBarBase = value.statusBarColor + navBarBase = value.navigationBarColor } } @@ -71,15 +77,16 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu invalidate() } - private var statusBarAlpha: Int = 255 - private var navBarAlpha: Int = 255 + private var statusBarBase: Int = 0 + private var navBarBase: Int = 0 val chromeFadeListener: SwipeListener by lazy { object : SwipeListener { override fun onScroll(percent: Float, px: Int, edgeFlag: Int) { + KL.d("PER $percent") activity?.apply { - if (edgeCurrentFlag != SWIPE_EDGE_TOP) statusBarColor = statusBarColor.withAlpha((statusBarAlpha * (1 - percent)).toInt()) - if (edgeCurrentFlag != SWIPE_EDGE_BOTTOM) navigationBarColor = navigationBarColor.withAlpha((navBarAlpha * (1 - percent)).toInt()) + statusBarColor = statusBarBase.adjustAlpha(scrimOpacity) + navigationBarColor = navBarBase.adjustAlpha(scrimOpacity) } } @@ -104,27 +111,26 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu * We will verify that only one axis is used at a time */ set(value) { + if (value !in arrayOf(SWIPE_EDGE_TOP, SWIPE_EDGE_BOTTOM, SWIPE_EDGE_LEFT, SWIPE_EDGE_RIGHT)) + throw SwipeBackException("Edge flag is not valid; use one of the SWIPE_EDGE_* values") field = value + horizontal = edgeFlag == SWIPE_EDGE_LEFT || edgeFlag == SWIPE_EDGE_RIGHT dragHelper.setEdgeTrackingEnabled(value) } - private val isHorizontal: Boolean - get() = edgeFlag and SWIPE_EDGE_HORIZONTAL > 0 + private var horizontal = true - private val isVertical: Boolean - get() = edgeFlag and SWIPE_EDGE_VERTICAL > 0 - - - /** - * Specifies the edge flag that should by considered at the current moment - */ - private var edgeCurrentFlag: Int = 0 - - private val isCurrentlyHorizontal: Boolean - get() = edgeCurrentFlag and SWIPE_EDGE_HORIZONTAL > 0 + override var minVelocity: Float + get() = dragHelper.minVelocity + set(value) { + dragHelper.minVelocity = value + } - private val isCurrentlyVertical: Boolean - get() = edgeCurrentFlag and SWIPE_EDGE_VERTICAL > 0 + override var maxVelocity: Float + get() = dragHelper.maxVelocity + set(value) { + dragHelper.maxVelocity = value + } init { dragHelper = ViewDragHelper.create(this, ViewDragCallback()) @@ -132,8 +138,8 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu val minVel = MIN_FLING_VELOCITY * density //allow touch from anywhere on the screen edgeSize = Math.max(resources.displayMetrics.widthPixels, resources.displayMetrics.heightPixels) - dragHelper.minVelocity = minVel - dragHelper.maxVelocity = 2 * minVel + minVelocity = minVel + maxVelocity = 2.5f * minVel edgeFlag = edgeFlag addListener(chromeFadeListener) } @@ -148,18 +154,6 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu contentView = view } - /** - * Set the size of an edge. This is the range in pixels along the edges of - * this view that will actively detect edge touches or drags if edge - * tracking is enabled. - - * @param swipeEdge The size of an edge in pixels - */ - // override fun setEdgeSize(swipeEdge: Int) { -// trackingEdge = swipeEdge -// } - - override fun setEdgeSizePercent(swipeEdgePercent: Float) { edgeSize = (resources.displayMetrics.widthPixels * swipeEdgePercent).toInt() } @@ -214,7 +208,7 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu inLayout = true val xOffset: Int val yOffset: Int - if (isCurrentlyHorizontal) { + if (horizontal) { xOffset = contentOffset yOffset = 0 } else { @@ -238,13 +232,20 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu } private fun drawScrim(canvas: Canvas, child: View) { - val baseAlpha = (scrimColor and 0xff000000.toInt()).ushr(24) - val alpha = (baseAlpha * scrimOpacity).toInt() - val color = alpha shl 24 or (scrimColor and 0xffffff) - canvas.clipRect(0, 0, width, height) + val color = scrimColor.adjustAlpha(scrimOpacity) + when (edgeFlag) { + SWIPE_EDGE_LEFT -> canvas.clipRect(0, 0, child.left, height) + SWIPE_EDGE_RIGHT -> canvas.clipRect(child.right, 0, width, height) + SWIPE_EDGE_TOP -> canvas.clipRect(0, 0, width, child.top) + SWIPE_EDGE_BOTTOM -> canvas.clipRect(0, child.bottom, width, height) + } canvas.drawColor(color) } + /** + * Extract content view, and move it from its parent to our view + * Then add our view to the content frame + */ fun attachToActivity(activity: Activity) { if (parent != null) return this.activity = activity @@ -252,24 +253,25 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu val background = a.getResourceId(0, 0) a.recycle() - val decor = activity.window.decorView as ViewGroup - var decorChild = decor.findViewById(android.R.id.content) - while (decorChild.parent !== decor) - decorChild = decorChild.parent as View - decorChild.setBackgroundResource(background) - decor.removeView(decorChild) - addView(decorChild) - setContentView(decorChild) - decor.addView(this) + val content = activity.window.decorView.findViewById(android.R.id.content) + val contentChild = content.getChildAt(0) + contentChild.setBackgroundResource(background) + content.removeView(contentChild) + addView(contentChild) + setContentView(contentChild) + content.addView(this) } + /** + * Remove ourselves from the viewstack + */ fun removeFromActivity(activity: Activity) { if (parent == null) return - val decorChild = getChildAt(0) as ViewGroup - val decor = activity.window.decorView as ViewGroup - decor.removeView(this) - removeView(decorChild) - decor.addView(decorChild) + val contentChild = getChildAt(0) + val content = activity.window.decorView.findViewById(android.R.id.content) + content.removeView(this) + removeView(contentChild) + content.addView(contentChild) } override fun computeScroll() { @@ -296,42 +298,35 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu * Needs to be bigger than 0 to specify that scrolling is possible horizontally */ override fun getViewHorizontalDragRange(child: View): Int { - return if (isHorizontal) 1 else 0 + return if (horizontal) 1 else 0 } /** * Needs to be bigger than 0 to specify that scrolling is possible vertically */ override fun getViewVerticalDragRange(child: View): Int { - return if (isVertical) 1 else 0 + return if (!horizontal) 1 else 0 } override fun onViewPositionChanged(changedView: View, left: Int, top: Int, dx: Int, dy: Int) { super.onViewPositionChanged(changedView, left, top, dx, dy) //make sure that we are using the proper axis - val horizontal = (isHorizontal && (!isVertical || dy == 0)) - edgeCurrentFlag = - if (horizontal) - if (left > 0) SWIPE_EDGE_LEFT else SWIPE_EDGE_RIGHT - else - if (top > 0) SWIPE_EDGE_BOTTOM else SWIPE_EDGE_TOP - scrollPercent = Math.abs( if (horizontal) left.toFloat() / contentView!!.width else (top.toFloat() / contentView!!.height)) contentOffset = if (horizontal) left else top - KL.d("poz $horizontal $scrollPercent $contentOffset") invalidate() if (scrollPercent < scrollThreshold && !isScrollOverValid) isScrollOverValid = true - listeners.forEach { it.get()?.onScroll(scrollPercent, contentOffset, edgeCurrentFlag) } + if (scrollPercent <= 1) + listeners.forEach { it.get()?.onScroll(scrollPercent, contentOffset, edgeFlag) } if (scrollPercent >= 1) { if (!(activity?.isFinishing ?: true)) { if (scrollPercent >= scrollThreshold && isScrollOverValid) { isScrollOverValid = false - listeners.forEach { it.get()?.onScrollToClose(edgeCurrentFlag) } + listeners.forEach { it.get()?.onScrollToClose(edgeFlag) } } activity?.finish() activity?.overridePendingTransition(0, 0) @@ -341,21 +336,19 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu override fun onViewReleased(releasedChild: View, xvel: Float, yvel: Float) { var result = Pair(0, 0) - KL.d("Release $edgeCurrentFlag $xvel $yvel") if (scrollPercent <= scrollThreshold) { //threshold not met; check velocities - if ((edgeCurrentFlag == SWIPE_EDGE_LEFT && xvel > MIN_FLING_VELOCITY) - || (edgeCurrentFlag == SWIPE_EDGE_RIGHT && xvel < -MIN_FLING_VELOCITY) - || (edgeCurrentFlag == SWIPE_EDGE_TOP && yvel < -MIN_FLING_VELOCITY) - || (edgeCurrentFlag == SWIPE_EDGE_BOTTOM && yvel > MIN_FLING_VELOCITY)) - result = exitCaptureOffsets(edgeCurrentFlag, releasedChild) + if ((edgeFlag == SWIPE_EDGE_LEFT && xvel > MIN_FLING_VELOCITY) + || (edgeFlag == SWIPE_EDGE_RIGHT && xvel < -MIN_FLING_VELOCITY) + || (edgeFlag == SWIPE_EDGE_TOP && yvel > MIN_FLING_VELOCITY) + || (edgeFlag == SWIPE_EDGE_BOTTOM && yvel < -MIN_FLING_VELOCITY)) + result = exitCaptureOffsets(edgeFlag, releasedChild) } else { //threshold met; fling to designated side - result = exitCaptureOffsets(edgeCurrentFlag, releasedChild) + result = exitCaptureOffsets(edgeFlag, releasedChild) } dragHelper.settleCapturedViewAt(result.first, result.second) invalidate() - edgeCurrentFlag = 0 } private fun exitCaptureOffsets(edgeFlag: Int, view: View): Pair { @@ -364,25 +357,22 @@ class SwipeBackLayout @JvmOverloads constructor(context: Context, attrs: Attribu when (edgeFlag) { SWIPE_EDGE_LEFT -> left = view.width + OVERSCROLL_DISTANCE SWIPE_EDGE_RIGHT -> left = -(view.width + OVERSCROLL_DISTANCE) - SWIPE_EDGE_TOP -> top = -(view.height + OVERSCROLL_DISTANCE) - SWIPE_EDGE_BOTTOM -> top = view.height + OVERSCROLL_DISTANCE - else -> KL.e("Unknown edgeCurrentFlag $edgeCurrentFlag") + SWIPE_EDGE_TOP -> top = view.height + OVERSCROLL_DISTANCE + SWIPE_EDGE_BOTTOM -> top = -(view.height + OVERSCROLL_DISTANCE) } return Pair(left, top) } override fun clampViewPositionHorizontal(child: View, left: Int, dx: Int): Int { - return if (!isHorizontal || isCurrentlyVertical) 0 - else if (edgeFlag == SWIPE_EDGE_RIGHT) Math.min(0, Math.max(left, -child.width)) + return if (edgeFlag == SWIPE_EDGE_RIGHT) Math.min(0, Math.max(left, -child.width)) else if (edgeFlag == SWIPE_EDGE_LEFT) Math.min(child.width, Math.max(left, 0)) - else Math.max(left, -child.width) + else 0 } override fun clampViewPositionVertical(child: View, top: Int, dy: Int): Int { - return if (!isVertical || isCurrentlyHorizontal) 0 - else if (edgeFlag == SWIPE_EDGE_BOTTOM) Math.min(0, Math.max(top, -child.height)) + return if (edgeFlag == SWIPE_EDGE_BOTTOM) Math.min(0, Math.max(top, -child.height)) else if (edgeFlag == SWIPE_EDGE_TOP) Math.min(child.height, Math.max(top, 0)) - else Math.max(top, -child.height) + else 0 } } diff --git a/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackPage.kt b/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackPage.kt index 4185f30..761e565 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackPage.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeBackPage.kt @@ -4,7 +4,6 @@ import android.app.Activity import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.view.ViewGroup -import ca.allanwang.kau.logging.KL /** * Created by Mr.Jude on 2015/8/3. @@ -38,7 +37,6 @@ class SwipeBackPage(activity: Activity) : SwipeBackContract by SwipeBackLayout(a } private fun handleLayout() { - KL.d("Handle layout") if (swipeEnabled) swipeBackLayout.attachToActivity(activity!!) else swipeBackLayout.removeFromActivity(activity!!) } @@ -57,9 +55,11 @@ interface SwipeBackContract { var scrimColor: Int val swipeBackLayout: SwipeBackLayout var edgeSize: Int - var edgeFlag:Int + var edgeFlag: Int var scrollThreshold: Float var disallowIntercept: Boolean + var minVelocity: Float + var maxVelocity: Float fun setEdgeSizePercent(swipeEdgePercent: Float) fun addListener(listener: SwipeListener) fun removeListener(listener: SwipeListener) diff --git a/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeListener.kt b/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeListener.kt index 71771a8..36a4445 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeListener.kt +++ b/core/src/main/kotlin/ca/allanwang/kau/swipe/SwipeListener.kt @@ -1,10 +1,17 @@ package ca.allanwang.kau.swipe interface SwipeListener { + /** + * Invoked as the page is scrolled + * Percent is capped at 1.0, even if there is a slight overscroll for the pages + */ fun onScroll(percent: Float, px: Int, edgeFlag: Int) + /** + * Invoked when page first consumes the scroll events + */ fun onEdgeTouch() /** - * Invoke when scroll percent over the threshold for the first time + * Invoked when scroll percent over the threshold for the first time */ fun onScrollToClose(edgeFlag: Int) } \ No newline at end of file diff --git a/core/src/main/kotlin/ca/allanwang/kau/swipe/ViewDragHelper.java b/core/src/main/kotlin/ca/allanwang/kau/swipe/ViewDragHelper.java index ad17366..db51ec1 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/swipe/ViewDragHelper.java +++ b/core/src/main/kotlin/ca/allanwang/kau/swipe/ViewDragHelper.java @@ -23,7 +23,7 @@ import java.util.Arrays; * views within their parent ViewGroup. *

* This is an extension of {@link android.support.v4.widget.ViewDragHelper} - * Along with additional methods defined in + * Along with additional methods defined in {@link ViewDragHelperExtras} */ public class ViewDragHelper implements ViewDragHelperExtras { private static final String TAG = "ViewDragHelper"; diff --git a/core/src/main/kotlin/ca/allanwang/kau/swipe/ViewDragHelperExtras.java b/core/src/main/kotlin/ca/allanwang/kau/swipe/ViewDragHelperExtras.java index 638f2fa..2ee7ea3 100644 --- a/core/src/main/kotlin/ca/allanwang/kau/swipe/ViewDragHelperExtras.java +++ b/core/src/main/kotlin/ca/allanwang/kau/swipe/ViewDragHelperExtras.java @@ -6,8 +6,7 @@ package ca.allanwang.kau.swipe; * Collection of addition methods added into ViewDragHelper that weren't in the original */ -public interface ViewDragHelperExtras { - +interface ViewDragHelperExtras { /** * Sets the new size of a given edge -- cgit v1.2.3