Java tutorial
/* * Copyright 2015 Eric Liu * * 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 cn.wyx.android.swipeback.swipe; import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPager; import android.support.v4.widget.ViewDragHelper; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.webkit.WebView; import android.widget.AbsListView; import android.widget.RelativeLayout; import android.widget.ScrollView; import java.lang.reflect.Field; import cn.wyx.android.swipeback.R; /** * Swipe or Pull to finish a Activity. * <p/> * This layout must be a root layout and contains only one direct child view. * <p/> * The activity must use a theme that with translucent style. * <style name="Theme.Swipe.Back" parent="AppTheme"> * <item name="android:windowIsTranslucent">true</item> * <item name="android:windowBackground">@android:color/transparent</item> * </style> * <p/> */ public class SwipeBackLayout extends RelativeLayout { private static final String TAG = "SwipeBackLayout"; private static final double AUTO_FINISHED_SPEED_LIMIT = 2000.0; private static final float BACK_FACTOR = 0.5f; private final ViewDragHelper viewDragHelper; private DragEdge dragEdge = DragEdge.LEFT; private View target; private View scrollChild; private int verticalDragRange = 0; private int horizontalDragRange = 0; private int draggingState = 0; private int draggingOffset; /** * Whether allow to pull this layout. */ private boolean enablePullToBack = true; /** * the anchor of calling finish. */ private float finishAnchor = 0; private boolean enableFlingBack = true; private Drawable mShadow; private SwipeBackListener swipeBackListener; /** * ? */ private Bitmap mLastScreenshot; private boolean isFullscreen; public SwipeBackLayout(Context context) { this(context, null); } public SwipeBackLayout(Context context, AttributeSet attrs) { super(context, attrs); setWillNotDraw(false); mShadow = getResources().getDrawable(R.drawable.left_shadow); viewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelperCallBack()); } public int getStatusBarHeight() { Class<?> c = null; Object obj = null; Field field = null; int x = 0, statusBarHeight = 0; try { c = Class.forName("com.android.internal.R$dimen"); obj = c.newInstance(); field = c.getField("status_bar_height"); x = Integer.parseInt(field.get(obj).toString()); statusBarHeight = getResources().getDimensionPixelSize(x); } catch (Exception e1) { e1.printStackTrace(); } return statusBarHeight; } public void setDragEdge(DragEdge dragEdge) { this.dragEdge = dragEdge; } /** * Set the anchor of calling finish. * * @param offset */ public void setFinishAnchor(float offset) { finishAnchor = offset; } /** * Whether allow to finish activity by fling the layout. * * @param b */ public void setEnableFlingBack(boolean b) { enableFlingBack = b; } @Deprecated public void setOnPullToBackListener(SwipeBackListener listener) { swipeBackListener = listener; } public void setLastScreenshot(Bitmap lastScreenshot) { this.mLastScreenshot = lastScreenshot; } public void setOnSwipeBackListener(SwipeBackListener listener) { swipeBackListener = listener; } public void setScrollChild(View view) { scrollChild = view; } public void setEnablePullToBack(boolean b) { enablePullToBack = b; } private void ensureTarget() { if (target == null) { if (getChildCount() > 1) { throw new IllegalStateException("SwipeBackLayout must contains only one direct child"); } target = getChildAt(0); if (scrollChild == null && target != null) { if (target instanceof ViewGroup) { findScrollView((ViewGroup) target); } else { scrollChild = target; } } } } /** * Find out the scrollable child view from a ViewGroup. * * @param viewGroup */ private void findScrollView(ViewGroup viewGroup) { scrollChild = viewGroup; if (viewGroup.getChildCount() > 0) { int count = viewGroup.getChildCount(); View child; for (int i = 0; i < count; i++) { child = viewGroup.getChildAt(i); if (child instanceof AbsListView || child instanceof ScrollView || child instanceof ViewPager || child instanceof WebView || child instanceof RecyclerView) { scrollChild = child; return; } } } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { viewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT); int width = getMeasuredWidth(); int height = getMeasuredHeight(); if (getChildCount() == 0) return; View child = getChildAt(0); int childWidth = width - getPaddingLeft() - getPaddingRight(); int childHeight = height - getPaddingTop() - getPaddingBottom(); int childLeft = getPaddingLeft(); int childTop = getPaddingTop(); int childRight = childLeft + childWidth; int childBottom = childTop + childHeight; child.layout(childLeft, childTop, childRight, childBottom); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (getChildCount() > 1) { throw new IllegalStateException("SwipeBackLayout must contains only one direct child."); } if (getChildCount() > 0) { int measureWidth = MeasureSpec.makeMeasureSpec( getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY); int measureHeight = MeasureSpec.makeMeasureSpec( getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY); getChildAt(0).measure(measureWidth, measureHeight); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); verticalDragRange = h; horizontalDragRange = w; switch (dragEdge) { case TOP: case BOTTOM: finishAnchor = finishAnchor > 0 ? finishAnchor : verticalDragRange * BACK_FACTOR; break; case LEFT: case RIGHT: finishAnchor = finishAnchor > 0 ? finishAnchor : horizontalDragRange * BACK_FACTOR; break; } } private int getDragRange() { switch (dragEdge) { case TOP: case BOTTOM: return verticalDragRange; case LEFT: case RIGHT: return horizontalDragRange; default: return verticalDragRange; } } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean handled = false; ensureTarget(); if (isEnabled()) { handled = viewDragHelper.shouldInterceptTouchEvent(ev); } else { viewDragHelper.cancel(); } return !handled ? super.onInterceptTouchEvent(ev) : handled; } @Override public boolean onTouchEvent(MotionEvent event) { viewDragHelper.processTouchEvent(event); return true; } @Override public void computeScroll() { if (viewDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } } public boolean canChildScrollUp() { return ViewCompat.canScrollVertically(scrollChild, -1); } public boolean canChildScrollDown() { return ViewCompat.canScrollVertically(scrollChild, 1); } private boolean canChildScrollRight() { return ViewCompat.canScrollHorizontally(scrollChild, -1); } private boolean canChildScrollLeft() { return ViewCompat.canScrollHorizontally(scrollChild, 1); } private void finish() { Activity act = (Activity) getContext(); act.finish(); act.overridePendingTransition(0, android.R.anim.fade_out); } private boolean backBySpeed(float xvel, float yvel) { switch (dragEdge) { case TOP: case BOTTOM: if (Math.abs(yvel) > Math.abs(xvel) && Math.abs(yvel) > AUTO_FINISHED_SPEED_LIMIT) { return dragEdge == DragEdge.TOP ? !canChildScrollUp() : !canChildScrollDown(); } break; case LEFT: case RIGHT: if (Math.abs(xvel) > Math.abs(yvel) && Math.abs(xvel) > AUTO_FINISHED_SPEED_LIMIT) { return dragEdge == DragEdge.LEFT ? !canChildScrollLeft() : !canChildScrollRight(); } break; } return false; } private void smoothScrollToX(int finalLeft) { if (viewDragHelper.settleCapturedViewAt(finalLeft, 0)) { ViewCompat.postInvalidateOnAnimation(SwipeBackLayout.this); } } private void smoothScrollToY(int finalTop) { if (viewDragHelper.settleCapturedViewAt(0, finalTop)) { ViewCompat.postInvalidateOnAnimation(SwipeBackLayout.this); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawLastScreenshot(canvas); drawShadow(canvas); } /** * ? * * @param canvas canvas */ private void drawLastScreenshot(Canvas canvas) { if (mLastScreenshot != null && target != null) { int right = target.getLeft(); Paint paint = new Paint(); paint.setAntiAlias(true); Rect src = new Rect(mLastScreenshot.getWidth() - target.getLeft(), 0, mLastScreenshot.getWidth(), target.getBottom()); //target?? Rect dst = new Rect(0, target.getBottom() - mLastScreenshot.getHeight(), right, target.getBottom()); int navigationBarHeight = getNavigationBarHeight(); isFullscreen = target.getHeight() == getResources().getDisplayMetrics().heightPixels + navigationBarHeight; int statusBarHeight = getStatusBarHeight(); switch (dragEdge) { case LEFT: src = new Rect(mLastScreenshot.getWidth() - target.getLeft(), 0, mLastScreenshot.getWidth(), mLastScreenshot.getHeight()); dst = new Rect(0, isFullscreen ? statusBarHeight : target.getBottom() - mLastScreenshot.getHeight(), right, isFullscreen ? target.getHeight() - navigationBarHeight : target.getBottom()); break; case RIGHT: src = new Rect(0, 0, mLastScreenshot.getWidth() - target.getRight(), mLastScreenshot.getHeight()); dst = new Rect(target.getRight(), isFullscreen ? statusBarHeight : target.getBottom() - mLastScreenshot.getHeight(), mLastScreenshot.getWidth(), isFullscreen ? target.getHeight() - navigationBarHeight : target.getBottom()); break; case TOP: src = new Rect(0, mLastScreenshot.getHeight() - target.getTop(), mLastScreenshot.getWidth(), target.getBottom()); dst = new Rect(0, 0, target.getRight(), target.getTop()); break; case BOTTOM: src = new Rect(0, 0, mLastScreenshot.getWidth(), mLastScreenshot.getHeight() - target.getBottom()); dst = new Rect(0, target.getBottom(), mLastScreenshot.getWidth(), mLastScreenshot.getHeight()); break; } canvas.drawBitmap(mLastScreenshot, src, dst, paint); } } /** * */ private void drawShadow(Canvas canvas) { if (target == null) { return; } switch (dragEdge) { case LEFT: mShadow.setBounds(target.getLeft() - mShadow.getIntrinsicWidth(), target.getTop(), target.getLeft(), target.getBottom()); break; case RIGHT: mShadow.setBounds(target.getRight(), target.getTop(), target.getRight() + mShadow.getIntrinsicWidth(), target.getBottom()); break; case TOP: mShadow.setBounds(target.getLeft(), target.getTop() - mShadow.getIntrinsicHeight(), target.getRight(), target.getTop()); break; case BOTTOM: mShadow.setBounds(target.getLeft(), target.getBottom(), target.getRight(), target.getBottom() + mShadow.getIntrinsicHeight()); break; } // drawable? // ? // Drawable mShadow.draw(canvas); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mLastScreenshot != null) { mLastScreenshot = null; } } private int getNavigationBarHeight() { Resources resources = getResources(); int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); //?NavigationBar return resources.getDimensionPixelSize(resourceId); } public enum DragEdge { LEFT, TOP, RIGHT, BOTTOM } public interface SwipeBackListener { /** * Return scrolled fraction of the layout. * * @param fractionAnchor relative to the anchor. * @param fractionScreen relative to the screen. */ void onViewPositionChanged(float fractionAnchor, float fractionScreen); } private class ViewDragHelperCallBack extends ViewDragHelper.Callback { @Override public boolean tryCaptureView(View child, int pointerId) { return child == target && enablePullToBack; } @Override public int getViewVerticalDragRange(View child) { return verticalDragRange; } @Override public int getViewHorizontalDragRange(View child) { return horizontalDragRange; } @Override public int clampViewPositionVertical(View child, int top, int dy) { int result = 0; if (dragEdge == DragEdge.TOP && !canChildScrollUp() && top > 0) { final int topBound = getPaddingTop(); final int bottomBound = verticalDragRange; result = Math.min(Math.max(top, topBound), bottomBound); } else if (dragEdge == DragEdge.BOTTOM && !canChildScrollDown() && top < 0) { final int topBound = -verticalDragRange; final int bottomBound = getPaddingTop(); result = Math.min(Math.max(top, topBound), bottomBound); } return result; } @Override public int clampViewPositionHorizontal(View child, int left, int dx) { int result = 0; if (dragEdge == DragEdge.LEFT && !canChildScrollRight() && left > 0) { final int leftBound = getPaddingLeft(); final int rightBound = horizontalDragRange; result = Math.min(Math.max(left, leftBound), rightBound); } else if (dragEdge == DragEdge.RIGHT && !canChildScrollLeft() && left < 0) { final int leftBound = -horizontalDragRange; final int rightBound = getPaddingLeft(); result = Math.min(Math.max(left, leftBound), rightBound); } return result; } @Override public void onViewDragStateChanged(int state) { if (state == draggingState) return; if ((draggingState == ViewDragHelper.STATE_DRAGGING || draggingState == ViewDragHelper.STATE_SETTLING) && state == ViewDragHelper.STATE_IDLE) { // the view stopped from moving. if (draggingOffset == getDragRange()) { finish(); } } draggingState = state; } @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { invalidate(); switch (dragEdge) { case TOP: case BOTTOM: draggingOffset = Math.abs(top); break; case LEFT: case RIGHT: draggingOffset = Math.abs(left); break; default: break; } //The proportion of the sliding. float fractionAnchor = (float) draggingOffset / finishAnchor; if (fractionAnchor >= 1) fractionAnchor = 1; float fractionScreen = (float) draggingOffset / (float) getDragRange(); if (fractionScreen >= 1) fractionScreen = 1; if (swipeBackListener != null) { swipeBackListener.onViewPositionChanged(fractionAnchor, fractionScreen); } } @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { if (draggingOffset == 0) return; if (draggingOffset == getDragRange()) return; boolean isBack = false; if (enableFlingBack && backBySpeed(xvel, yvel)) { isBack = !canChildScrollUp(); } else if (draggingOffset >= finishAnchor) { isBack = true; } else if (draggingOffset < finishAnchor) { isBack = false; } int finalLeft; int finalTop; switch (dragEdge) { case LEFT: finalLeft = isBack ? horizontalDragRange : 0; smoothScrollToX(finalLeft); break; case RIGHT: finalLeft = isBack ? -horizontalDragRange : 0; smoothScrollToX(finalLeft); break; case TOP: finalTop = isBack ? verticalDragRange : 0; smoothScrollToY(finalTop); break; case BOTTOM: finalTop = isBack ? -verticalDragRange : 0; smoothScrollToY(finalTop); break; } } } }