滑動退出Activity的兩種方法

最近遇到需求,需要滑動退出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′|┛ 如果你有好的想法思路,歡迎共享討論。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章