滑动退出Activity的两种方法

我只是一个虾纸丫 提交于 2019-12-02 12:34:19

最近遇到需求,需要滑动退出Activity,参考了大虾们的方案后整理出了两种主流的方法:

  • 使用OnTouchEvent,处理触摸事件实现滑动退出
  • 使用ViewDragHelper拖动实现滑动退出

两种方法各有利弊,遇到界面上的滑动或滚动事件产生冲突的需要自己处理,下面就来详细的介绍两种实现方法。

0.前提

两种方法不管使用哪一种都需要设置透明主题及Activity中根布局的background,以实现滑动时,上一个Activity可见。

Activity根布局背景:
    android:background="?android:colorBackground"
Activity主题:
    <style name="Translucent" parent="AppTheme">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowAnimationStyle">@android:style/Animation</item>
    </style>

1.使用OnTouchEvent,处理触摸事件实现滑动退出

先来看看具体实现:

/**
 * @author Steven Duan
 * @version 1.0
 */

public class SlideLayout extends FrameLayout {

  private static final String T = SlideLayout.class.getName();

  private Activity mActivity;
  private Scroller mScroller;
  private int mShadowWidth;
  private Drawable mLeftShadow;
  private int mLastMoveX;
  private int mScreenWidth;
  private int mMinX;

  public SlideLayout(Activity activity) {
    this(activity, null);
  }

  public SlideLayout(Activity activity, AttributeSet attrs) {
    this(activity, attrs, 0);
  }

  public SlideLayout(Activity activity, AttributeSet attrs, int defStyleAttr) {
    super(activity, attrs, defStyleAttr);
    Log.d(T, "F初始化 ");

    this.mActivity = activity;
    mScroller = new Scroller(activity);
    //滑动时渐变的阴影
    //noinspection deprecation  
    mLeftShadow = getResources().getDrawable(R.drawable.left_shadow);
    //阴影的宽度
    mShadowWidth = ((int) getResources().getDisplayMetrics().density) * 16;
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
        mLastMoveX = (int) event.getX();
        mScreenWidth = getWidth();
        mMinX = mScreenWidth / 10;
        break;
      case MotionEvent.ACTION_MOVE:
        int eventX = (int) event.getX();
        Log.d(T, "eventX: " + eventX);
        int dx = mLastMoveX - eventX;
        if (getScrollX() + dx >= 0) {
          scrollTo(0, 0);
        } else if (eventX > mMinX) {
          //手指处于屏幕边缘时不处理滑动
          scrollBy(dx, 0);
        }
        mLastMoveX = eventX;
        break;
      case MotionEvent.ACTION_UP:
        if (-getScrollX() < mScreenWidth / 2) {
          slideBack();
        } else {
          slideFinish();
        }
        break;
    }
    return true;
  }

  private void slideFinish() {
    mScroller.startScroll(getScrollX(), 0, -getScrollX() - mScreenWidth, 0, 200);
    invalidate();
  }

  private void slideBack() {
    mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0, 200);
    invalidate();
  }

  @Override
  public void computeScroll() {
    super.computeScroll();
    if (mScroller.computeScrollOffset()) {
      scrollTo(mScroller.getCurrX(), 0);
      postInvalidate();
    } else if (-getScrollX() == mScreenWidth) {
      mActivity.finish();
    }
  }

  @Override
  protected void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(canvas);
    //绘制边缘阴影
    canvas.save();
    //设置阴影的大小范围
    mLeftShadow.setBounds(0, 0, mShadowWidth, getHeight());
    //平移画布
    canvas.translate(-mShadowWidth, 0);
    //绘制
    mLeftShadow.draw(canvas);
    canvas.restore();
  }


  public void bind() {
    Log.d(T, "bind: F");
    ViewGroup decorView = (ViewGroup) mActivity.getWindow().getDecorView();
    View child = decorView.getChildAt(0);
    decorView.removeView(child);
    addView(child);
    decorView.addView(this);
    Log.d(T, "bind成功");
  }
}

定义好了容器,那么在Activity中如何使用呢?只需要在Activity的onCreate方法做如下调用即可:

@Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    new SlideLayout(this).bind();
  }

代码比较简单,一看便知。只是这种方法在Activity中有滑动或滚动事件会产生冲突,这就涉及事件分发机制,需要自己看情况处理。

2.使用ViewDragHelper拖动实现滑动退出

废话不多说直接上代码:

/**
 * @author Steven Duan
 * @version 1.0
 */

public class SlideLayout extends FrameLayout {

  private View mDragView;
  private Activity mActivity;
  private ViewDragHelper mViewDragHelper;
  private float mSlideWidth;
  private int mScreenWidth;
  private int mScreenHeight;
  private int curSlideX;

  public SlideLayout(Context context) {
    super(context);
    init(context);
  }

  private void init(Context context) {
    mActivity = (Activity) context;
    mViewDragHelper = ViewDragHelper.create(this, new DragCallback());
    //设置可拖动的边缘
    mViewDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
  }

  /**
   * 把DecorView下的子View移除添加到SlideLayout,再将SlideLayout添加到DecorView
   */
  public void bind() {
    ViewGroup mDecorView = (ViewGroup) mActivity.getWindow().getDecorView();
    mDragView = mDecorView.getChildAt(0);
    mDecorView.removeView(mDragView);
    this.addView(mDragView);
    mDecorView.addView(this);

    //计算屏幕宽度
    DisplayMetrics dm = new DisplayMetrics();
    mActivity.getWindowManager().getDefaultDisplay().getMetrics(dm);
    mScreenWidth = dm.widthPixels;
    mScreenHeight = dm.heightPixels;
    //触发Activity滑出的宽度
    mSlideWidth = dm.widthPixels * 0.5f;
  }

  @Override
  public boolean onInterceptHoverEvent(MotionEvent event) {
    return mViewDragHelper.shouldInterceptTouchEvent(event);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    mViewDragHelper.processTouchEvent(event);
    return true;
  }

  class DragCallback extends ViewDragHelper.Callback {

    @Override
    public boolean tryCaptureView(View child, int pointerId) {
      return false;
    }

    @Override
    public void onViewReleased(View releasedChild, float xvel, float yvel) {
      int left = releasedChild.getLeft();
      if (left <= mSlideWidth) {
        //返回
        mViewDragHelper.settleCapturedViewAt(0, 0);
      } else {
        //滑出
        mViewDragHelper.settleCapturedViewAt(mScreenWidth, 0);
      }
      //需要重绘
      invalidate();
    }

    @Override
    public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
      curSlideX = left;
      invalidate();
      if (changedView == mDragView && left >= mScreenWidth) {
        mActivity.finish();
      }
    }

    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
      //限制左右拖拽的位移
      left = left >= 0 ? left : 0;
      return left;
    }

    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {
      return 0;
    }

    @Override
    public void onEdgeDragStarted(int edgeFlags, int pointerId) {
      //触发边缘时,主动捕捉mDragView
      mViewDragHelper.captureChildView(mDragView, pointerId);
    }

  }

  @Override
  public void computeScroll() {
    //持续滚动期间,不断重绘
    if (mViewDragHelper.continueSettling(true))
      invalidate();

  }

  @Override
  protected void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(canvas);
    //进行阴影绘制
    canvas.save();
    Shader mShader = new LinearGradient(curSlideX - 40, 0, curSlideX, 0,
        new int[]{Color.parseColor("#00000000"), Color.parseColor("#50000000")},
        null, Shader.TileMode.REPEAT);
    //绘制阴影画笔
    Paint mPaint = new Paint();
    mPaint.setStrokeWidth(2);
    mPaint.setAntiAlias(true);
    mPaint.setColor(Color.GRAY);
    mPaint.setShader(mShader);
    RectF rectF = new RectF(curSlideX - 40, 0, curSlideX, mScreenHeight);
    canvas.drawRect(rectF, mPaint);
    canvas.restore();
  }
}

在Activity的onCreate()中调用:

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_forth);
    //一句话实现绑定
    new SlideLayout(this).bind();
  }

小结:两种方法归根结底都是把DecorView下的子View移除添加到SlideLayout,再将SlideLayout添加到DecorView这种思路,只是移动的方式不同而已。再说第二种方法使用ViewDragHelper只能从边缘拖拽,这显得不那么友好。

┗|`O′|┛ 如果你有好的想法思路,欢迎共享讨论。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!