1. 程式人生 > >【android開發】手勢滑動關閉Activity(隨手指消失)的輔助類的實現

【android開發】手勢滑動關閉Activity(隨手指消失)的輔助類的實現

【CSDN抽風,把我寫一個多小時的東西覆蓋了。真的是嗶了狗了,自己又沒有備份。。。重寫吧。。。】

這個類主要是實現向右滑動關閉Activity,效果如下:
手勢滑動關閉Activity效果

老套路,先寫思路:
1)將Activity的背景設定為透明模式。(從而可以看到下一層Activity)
2)建立一個FrameLayout,將contentView從DecorView中移除,並將contentView新增到我們的FrameLayout中,最後再將我們的FrameLayout添加回原來contentView的地方。
【熟悉android ViewTree的人應該能輕鬆理解contentView和DecorView。針對不熟悉的朋友,我做簡單介紹:我們將Activity介面看做由ActionBar部分 + ContentView部分(onCreate中的setContentView方法設定的部分) 組成,如果我們設定為NoActionBar型別的主題時,ActionBar部分將不存在,整個Activity都由ContentView部分組成。而Activity會有個rootView來容納他們,這個rootView就是DecorView。】示意圖如下:
DecorView示意圖


3)實現FrameLayout的攔截邏輯,當手勢向右滑動時,攔截事件,將contentView進行水平方向移動。

分析完開動程式碼。

1、建立自定義ViewGroup類並申明必須要的引數,程式碼如下:

class MySwipeView extends FrameLayout {

    private Activity activity;// 繫結的Activity
    private ViewGroup decorView;
    private View contentView;// activity的ContentView
    private float intercept_X = 0
;// onInterceptTouchEvent剛觸控時的X座標 private float intercept_Y = 0;// onInterceptTouchEvent手指剛觸控時的y座標 private int touchSlop = 0;// 產生滑動的最小值 public MySwipeView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); touchSlop = ViewConfiguration.get
(getContext()) .getScaledTouchSlop(); setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); } public MySwipeView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MySwipeView(Context context) { this(context, null); } }

2、實現Activity的繫結,並將contentView從decorView中移除,然後新增到我們的FrameLayout中,最後將我們的FrameLayout添加回DecorView。程式碼如下:

/**
 * 繫結Activity
 * @param activity
 */
public void setActivity(Activity activity) {
    this.activity = activity;
    initCoverView();
}

/**
 * 將contentView從DecorView中移除,並新增到CoverView中,最後再將CoverView新增到DecorView中
 */
private void initCoverView() {
    decorView = (ViewGroup) activity.getWindow().getDecorView();
    // decorView.setBackgroundColor(Color.parseColor("#33000000"));
    contentView = (ViewGroup) decorView
            .findViewById(android.R.id.content);
    ViewGroup contentParent = (ViewGroup) contentView.getParent();
    contentParent.removeView(contentView);
    addView(contentView);
    contentView.setBackgroundColor(Color.WHITE);
    contentParent.addView(this);
}

3、實現事件攔截程式碼。說到事件攔截,熟悉android事件分發機制的朋友,應該知道。ViewGroup有個public boolean onInterceptTouchEvent(MotionEvent ev)方法,當此方法返回true時,ViewGroup將不會傳遞事件給他的childView,同時會呼叫該ViewGroup的onTouchEvent來處理該事件;反之,返回false時,事件將會傳遞給他的childView處理。我們通過實現onInterceptTouchEvent的邏輯來過濾我們需要的事件。程式碼如下,看一兩遍理解沒問題的:

@Override
public boolean onInterceptTouchEvent(android.view.MotionEvent ev) {

        return shouldInterceptEvent(ev);
};


/**
 * 判斷是否應該攔截事件。
 * 如果水平方向的偏移量(不取絕對值) > 垂直方向的偏移量(取絕對值),並且水平方向的偏移量大於最小滑動距離,我們將攔截事件。
 * 【實際過程中,我們發現touchSlope還是偏小,所以取了其3倍的數值作為最小滑動距離】
 * @param event
 *            事件物件
 * @return true表示攔截,false反之
 */
private boolean shouldInterceptEvent(MotionEvent event) {
    boolean shouldInterceptEvent = false;
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
        intercept_X = event.getX();
        intercept_Y = event.getY();
        break;
    case MotionEvent.ACTION_MOVE:
        float offsetY = Math.abs(event.getY() - intercept_Y);
        float offsetX = Math.abs(event.getX() - intercept_X);
        if (offsetY >= touchSlop * 3 || offsetY > offsetX) {
            shouldInterceptEvent = false;
        } else if (event.getX() - intercept_X >= touchSlop * 3) {
            shouldInterceptEvent = true;
        } else {
            shouldInterceptEvent = false;
        }
        break;
    case MotionEvent.ACTION_UP:
        shouldInterceptEvent = false;
        break;
    default:
        break;
    }
    return shouldInterceptEvent;
}

4、實現onTouchEvent程式碼:

@Override
public boolean onTouchEvent(android.view.MotionEvent event) {

            processTouchEvent(event);
            return true;
};


/**
 * 對onTouchEvent事件進行處理
 * @param event
 *            事件物件
 */
private void processTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
        break;

    case MotionEvent.ACTION_MOVE:
    //如果是向右滑動,我們動態改變contentView的便宜值
        float offsetX = event.getX() - intercept_X;
        if (offsetX > 0) {
            contentView.setTranslationX(offsetX);
        }
        break;

    case MotionEvent.ACTION_UP:
    //如果手釋放時是在螢幕的1/3之內,我們視為使用者不想關閉Activity,則彈回。反之,關閉
        if (contentView.getTranslationX() >= contentView
                .getMeasuredWidth() / 3) {
            collapse();
        } else {
            open();
        }
        break;

    default:
        break;
    }
}


/**
 * 展開Activity
 */
private void open() {
    contentView.clearAnimation();
    ObjectAnimator anim = ObjectAnimator.ofFloat(contentView,
            View.TRANSLATION_X, 0);
    anim.start();

}

/**
 * 摺疊Activity(finish掉)
 */
private void collapse() {
    contentView.clearAnimation();
    ObjectAnimator anim = ObjectAnimator.ofFloat(contentView,
            View.TRANSLATION_X, contentView.getMeasuredWidth());
    anim.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            activity.finish();
        }
    });
    anim.start();

}

5、到此,我們基本完成了FrameLayout的開發。我們最後可以將此FrameLayout放到一個類中包裹起來,做成一個工具類。(可選)。程式碼如下:

public class SwipeToFinishView {
    private MySwipeView mySwipeView;

    public SwipeToFinishView(Activity activity) {
        mySwipeView = new MySwipeView(activity);
        mySwipeView.setActivity(activity);
    }

//核心View
private class MySwipeView extends FrameLayout {
        .....
    }
}

6、將Activity設定為背景透明模式。
1)新建透明Activity的style。程式碼形如:

<style name="SwipTheme" parent="BaseTheme">
    <item name="android:windowBackground">@android:color/transparent</item> 
    <item name="android:windowIsTranslucent">true</item>   
    <item name="android:windowAnimationStyle">
        @android:style/Animation.Translucent
    </item>
</style> 

2)應用此Style到Activity,在AndroidManifest檔案中指定的Activity程式碼新增程式碼如下:

android:theme="@style/SwipTheme"

7、如何使用:
在Activity的setContentView之後,new此類即可,程式碼如下。

SwipeToFinishView swipeToFinishView= new SwipeToFinishView(this); 

【 使 用 前 提 】
1、該Activity的主題應該是NoActionBar(沒有ActionBar)
2、該Activity使用的主題應該包含下面三個item(用於將Activity的背景透明)

<item name="android:windowBackground">@android:color/transparent </item> 
<item name="android:windowIsTranslucent">true </item> 
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent </item>

3、並在style.xml中新增加一個Style應用到該Activity的主題上。新增的style程式碼如下:


<style name="SwipTheme" parent="BaseTheme">
    <item name="android:windowBackground">@android:color/transparent</item> 
    <item name="android:windowIsTranslucent">true</item>   
    <item name="android:windowAnimationStyle" >@android:style/Animation.Translucent</item> </style> 

最後在Activity的申明中新增下面的程式碼:

android:theme="@style/SwipTheme"

【 總 結 】

這個類寫的很簡單,基本就是一個ViewGroup的攔截程式碼和處理程式碼,但是中間也需要了一些小問題。
剛開始,我是使用的ViewDragHelper來做的,但是,後來當Activity有可獲取焦點型別的View時(例如使用了ListView),ViewDragHelper無法正常工作。無奈之下自己手動實現攔截程式碼。

老樣子,原始碼如下。


/**
 * 跟隨手勢向右滑動消失的View幫助類。<br/>
 * 使用方法:在Activity的setContentView之後,new此類即可,程式碼如下。<br/>
 * SwipeToFinishView swipeToFinishView= new SwipeToFinishView(this);
 * <hr/>
 * 使用前提:<br/>
 * <ol>
 * <li>該Activity的主題應該是NoActionBar(沒有ActionBar)</li>
 * <li>該Activity使用的主題應該包含下面三個item(用於將Activity的背景透明)
 * <ul>
 * <li>
 * 
 * &lt;item name="android:windowBackground">@android:color/transparent
 * &lt;/item>
 * 
 * </li>
 * <li>
 * 
 * &lt;item name="android:windowIsTranslucent">true &lt;/item>
 * 
 * </li>
 * <li>
 * 
 * &lt;item
 * name="android:windowAnimationStyle">@android:style/Animation.Translucent
 * &lt;/item>
 * 
 * </li>
 * 
 * </ul>
 * </li>
 * </ol>
 * 例如:我想給SecondActivity新增滑動消失功能,我就會在onCreate中新增程式碼:<br/>
 * SwipeToFinishView swipeToFinishView= new SwipeToFinishView(this);<br/>
 * 並在style.xml中新增加一個Style應用到該Activity的主題上。新增的style程式碼如下:<br/>
 * <!-- SecondActivity theme. --> <br/>
 * &lt;style name="SwipTheme" parent="BaseTheme"><br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;item
 * name="android:windowBackground">@android:color/transparent&lt;/item> <br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;item
 * name="android:windowIsTranslucent">true&lt;/item>   <br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;item name="android:windowAnimationStyle"
 * >@android:style/Animation.Translucent&lt;/item> &lt;/style> <br/>
 * 最後在Activity的申明中新增下面的程式碼:<br/>
 * android:theme="@style/SwipTheme"
 * 
 * @author 藍亭書序 2016.11.29
 * 
 */
public class SwipeToFinishView {
    private MySwipeView mySwipeView;

    public SwipeToFinishView(Activity activity) {
        mySwipeView = new MySwipeView(activity);
        mySwipeView.setActivity(activity);
    }

    /**
     * 核心View
     * 
     * @author 藍亭書序
     * 
     */
    private class MySwipeView extends FrameLayout {

        private Activity activity;// 繫結的Activity
        private ViewGroup decorView;
        private View contentView;// activity的ContentView
        private float intercept_X = 0;// onInterceptTouchEvent剛觸控時的X座標
        private float intercept_Y = 0;// onInterceptTouchEvent手指剛觸控時的y座標
        private int touchSlop = 0;// 產生滑動的最小值

        public MySwipeView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            touchSlop = ViewConfiguration.get(getContext())
                    .getScaledTouchSlop();
            setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
                    LayoutParams.MATCH_PARENT));
        }

        public MySwipeView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }

        public MySwipeView(Context context) {
            this(context, null);
        }

        @Override
        public boolean onInterceptTouchEvent(android.view.MotionEvent ev) {
            return shouldInterceptEvent(ev);
        };

        @SuppressLint("ClickableViewAccessibility")
        public boolean onTouchEvent(android.view.MotionEvent event) {
            processTouchEvent(event);
            return true;
        };

        /**
         * 繫結Activity
         * 
         * @param activity
         */
        public void setActivity(Activity activity) {
            this.activity = activity;
            initCoverView();
        }

        /**
         * 將contentView從DecorView中移除,並新增到CoverView中,最後再將CoverView新增到DecorView中
         */
        private void initCoverView() {
            decorView = (ViewGroup) activity.getWindow().getDecorView();
            // decorView.setBackgroundColor(Color.parseColor("#33000000"));
            contentView = (ViewGroup) decorView
                    .findViewById(android.R.id.content);
            ViewGroup contentParent = (ViewGroup) contentView.getParent();
            contentParent.removeView(contentView);
            addView(contentView);
            contentView.setBackgroundColor(Color.WHITE);
            contentParent.addView(this);
        }

        /**
         * 判斷是否應該攔截事件
         * 
         * @param event
         *            事件物件
         * @return true表示攔截,false反之
         */
        private boolean shouldInterceptEvent(MotionEvent event) {
            boolean shouldInterceptEvent = false;
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                intercept_X = event.getX();
                intercept_Y = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float offsetY = Math.abs(event.getY() - intercept_Y);
                float offsetX = Math.abs(event.getX() - intercept_X);
                if (offsetY >= touchSlop * 3 || offsetY > offsetX) {
                    shouldInterceptEvent = false;
                } else if (event.getX() - intercept_X >= touchSlop * 3) {
                    shouldInterceptEvent = true;
                } else {
                    shouldInterceptEvent = false;
                }
                break;

            case MotionEvent.ACTION_UP:
                shouldInterceptEvent = false;
                break;
            default:
                break;
            }
            return shouldInterceptEvent;
        }

        /**
         * 對onTouchEvent事件進行處理
         * 
         * @param event
         *            事件物件
         */
        private void processTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;

            case MotionEvent.ACTION_MOVE:
                float offsetX = event.getX() - intercept_X;
                if (offsetX > 0) {
                    contentView.setTranslationX(offsetX);
                }
                break;

            case MotionEvent.ACTION_UP:
                if (contentView.getTranslationX() >= contentView
                        .getMeasuredWidth() / 3) {
                    collapse();
                } else {
                    open();
                }
                break;

            default:
                break;
            }
        }

        /**
         * 展開Activity
         */
        private void open() {
            contentView.clearAnimation();
            ObjectAnimator anim = ObjectAnimator.ofFloat(contentView,
                    View.TRANSLATION_X, 0);
            anim.start();

        }

        /**
         * 摺疊Activity(finish掉)
         */
        private void collapse() {
            contentView.clearAnimation();
            ObjectAnimator anim = ObjectAnimator.ofFloat(contentView,
                    View.TRANSLATION_X, contentView.getMeasuredWidth());
            anim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    activity.finish();
                }
            });
            anim.start();

        }

    }
}