【右滑返回】滑動衝突 Scroller DecorView

基本思想

咱們的滑動邏輯主要是利用View的scrollBy() 方法, scrollTo()方法和Scroller類來實現的
當手指拖動視圖的時候,咱們監聽手指在屏幕上滑動的距離
利用View的scrollBy() 方法使得View隨着手指的滑動而滑動
而當手指離開屏幕,咱們在根據邏輯使用Scroller類startScroll()方法設置滑動的參數,而後再根據View的scrollTo進行滾動。

對於View的滑動,存在一些Touch事件消費的處理等問題,最主要的就是Activity裏面有一些ListView、 GridView、ScrollView等控件
假如咱們Activity裏面存在ListView、GridView等控件的話,咱們對Activity的最外層佈局進行滾動根本就無效果,由於Touch事件被ListView、GridView等控件消費了,因此Activity的最外層佈局根本得不到Touch事件,也就實現不了Touch邏輯了
爲了解決此Touch事件問題,咱們將OnTouchListener直接設置到ListView、GridView上面,這樣子就避免了Activity的最外層接受不到Touch事件的問題了

核心:一個自定義View

public class SwipeBackLayout extends FrameLayout {
    //常量
    /**手指向右滑動時的最小【滑動】距離(只有滑動超過此距離才滾動view)*/
    public static int X_MIN_DISTANCE_IF_MOVE = 8;
    /**手指向右滑動時的最大【起始】距離(防止誤操做,只有從左邊緣滑動纔有效),這個值最好大於上面的值*/
    public static int X_MIN_START_DISTANCE = 10;
    /**手指向右滑動時的最小【滑動】距離(防止誤操做,只有滑動超過必定距離才關閉)*/
    public static int X_MIN_DISTANCE_FROM_LEFT = 90;
    /**自動滾動到左側(回到初始位置)消耗的時間*/
    public static final int TIME_MOVE_TO_LEFT = 20;
    /**自動滾動到右側(關閉應用前)消耗的時間*/
    public static final int TIME_MOVE_TO_RIGHT = 200;

    //一些能夠設置的成員
    /**滑動時左邊緣是否顯示陰影*/
    private boolean isShowShadow = true;
    /**當touch位置有ViewPager,可是ViewPager不是在item0時,是否攔截【從屏幕邊緣down】的滑動事件*/
    private boolean isInterceptWhenTouchViewPagerIfNotFirst = false;
    /**滑動時左邊緣添加陰影*/
    private Drawable mShadowDrawable = getResources().getDrawable(R.drawable.shadow_left);

    //臨時變量
    /**是否要finish掉Activity*/
    private boolean isFinish;
    private Activity mActivity;
    /**記錄按下時的觸摸點、移動時的觸摸點在屏幕上的X座標*/
    private int downXtouchX;
    private Scroller mScroller = new Scroller(getContext());
    private List<ViewPager> mViewPagers = new LinkedList<ViewPager>();

    public SwipeBackLayout(Context context) {
        this(context, null);
    }
    public SwipeBackLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public SwipeBackLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        DisplayMetrics metric = new DisplayMetrics();
        ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metric);
        X_MIN_DISTANCE_FROM_LEFT = (int) (metric.widthPixels * 0.2f);//屏幕寬的1/5
        //滑動的時候,手的移動大於這個距離纔開始移動控件;若是小於這個距離就不觸發移動控件。ViewPage就是用這個距離來判斷用戶是否翻頁的
        X_MIN_DISTANCE_IF_MOVE = ViewConfiguration.get(context).getScaledTouchSlop();
        Log.i("bqt"X_MIN_DISTANCE_FROM_LEFT + "---" + X_MIN_DISTANCE_IF_MOVE);//144-16
        X_MIN_START_DISTANCE = X_MIN_DISTANCE_IF_MOVE + 2;//這個值最好大於X_MIN_DISTANCE_IF_MOVE
    }
    //必須手動調用的方法
    public void attachToActivityAndAsRootLayout(Activity activity) {
        mActivity = activity;
        FrameLayout decorView = (FrameLayout) activity.getWindow().getDecorView();//全部窗口的根View
        ViewGroup decorChild = (ViewGroup) decorView.getChildAt(0);//封裝內容區域和ActionBar區域的容器
        decorView.removeView(decorChild);
         this. addView ( decorChild ) ;
        decorView.addView(this);
    }
    //******************************************************************************************
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (!isInterceptWhenTouchViewPagerIfNotFirst) {
            ViewPager mViewPager = getMyTouchViewPager(mViewPagers, event);
            //若是存在ViewPager而且ViewPager不是處在第一個Item,咱們才攔截Touch事件,不然不攔截(Touch事件由ViewPager處理)
            if (mViewPager != null && mViewPager.getCurrentItem() != 0) return super.onInterceptTouchEvent(event);
        }
        switch (event.getActionMasked()) {
        case MotionEvent.ACTION_DOWN:
            downX = (int) event.getRawX();//getRawX獲取的是相對父View(也即整個屏幕左上角)的位置座標
            touchX = downX;
            break;
        case MotionEvent.ACTION_MOVE:
            if (downX <= X_MIN_START_DISTANCE //不是從屏幕左邊緣開始的不攔截
                    && event.getRawX() - downX > X_MIN_DISTANCE_IF_MOVE) return true;//滑動距離過小時暫不攔截
            break;
        }
        return super.onInterceptTouchEvent(event);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
        case MotionEvent.ACTION_MOVE:
            if (event.getRawX() - downX > X_MIN_DISTANCE_IF_MOVE) scrollBy(touchX - (int) event.getRawX(), 0);//將View中的內容滾動指定距離
            touchX = (int) event.getRawX();
            break;
        case MotionEvent.ACTION_UP:
            if (Math.abs(getScrollX()) >= X_MIN_DISTANCE_FROM_LEFT) scrollRightOrLeft(true);//當滑動的距離大於咱們設定的最小距離時,滑到右側
            else scrollRightOrLeft(false);//當滑動的距離小於咱們設定的最小距離時,回到起始位置
            break;
        }
        return true;
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (changed) getAlLViewPager(mViewPagersthis);//【遞歸】遍歷整個View樹,獲取裏面的ViewPager的集合
    }
    @Override
    protected void dispatchDraw(Canvas canvas) {//調用View.onDraw爲繪製VIew自己,調用dispatchDraw爲繪製本身的孩子
        //Called by draw to draw the child views. This may be overridden by derived classes to gain control just before its children are drawn 
        super.dispatchDraw(canvas);
        if (isShowShadow && mShadowDrawable != null) {
            int left = getLeft() - mShadowDrawable.getIntrinsicWidth();
            int right = left + mShadowDrawable.getIntrinsicWidth();
            mShadowDrawable.setBounds(left, getTop(), right, getBottom());
            mShadowDrawable.draw(canvas);
        }
    }
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX()mScroller.getCurrY());
            postInvalidate();
            if (mScroller.isFinished() && isFinish) mActivity.finish();
        }
    }
    //******************************************************************************************
    /**
     * 【遞歸】遍歷整個View樹,獲取裏面的ViewPager的集合
     */
    private void getAlLViewPager(List<ViewPager> mViewPagers, ViewGroup parent) {
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            if (child instanceof ViewPager) mViewPagers.add((ViewPager) child);
            else if (child instanceof ViewGroup) getAlLViewPager(mViewPagers, (ViewGroup) child);
        }
    }
    /**
     * 返回咱們touch範圍內的那個ViewPager
     */
    private ViewPager getMyTouchViewPager(List<ViewPager> mViewPagers, MotionEvent ev) {
        if (mViewPagers == null || mViewPagers.size() == 0) return null;
        Rect mRect = new Rect();
        for (ViewPager viewPager : mViewPagers) {
            viewPager.getHitRect(mRect);
            if (mRect.contains((int) ev.getX()(int) ev.getY())) return viewPager;
        }
        return null;
    }
    /**
     * 滾動出界面或滾動到起始位置
     */
    private void scrollRightOrLeft(boolean toRight) {
        isFinish = toRight;
        if (toRight) mScroller.startScroll(getScrollX(), 0, -getWidth() - getScrollX(), 0, TIME_MOVE_TO_RIGHT);
        else mScroller.startScroll(getScrollX(), 0, -getScrollX(), 0, TIME_MOVE_TO_LEFT);//int startX, int startY, int dx, int dy, int duration
        postInvalidate();//刷新界面
    }
    //get和set方法******************************************************************************************
    public boolean isInterceptWhenTouchViewPagerIfNotFirst() {
        return isInterceptWhenTouchViewPagerIfNotFirst;
    }
    public void setInterceptWhenTouchViewPagerIfNotFirst(boolean isInterceptWhenTouchViewPagerIfNotFirst) {
        this.isInterceptWhenTouchViewPagerIfNotFirst = isInterceptWhenTouchViewPagerIfNotFirst;
    }
    public Drawable getmShadowDrawable() {
        return mShadowDrawable;
    }
    public void setmShadowDrawable(Drawable mShadowDrawable) {
        this.mShadowDrawable = mShadowDrawable;
    }
    public boolean isShowShadow() {
        return isShowShadow;
    }
    public void setShowShadow(boolean isShowShadow) {
        this.isShowShadow = isShowShadow;
    }
}

Activity基類

/**
 * 想要實現向右滑動刪除Activity效果只須要繼承SwipeBackActivity便可
 */
public class SwipeBackActivity extends Activity {
    protected SwipeBackLayout rootLayout;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);//能夠放在super.onCreate以後,但必須放在attachToActivityAndAsRootLayout以前
        super.onCreate(savedInstanceState);
        rootLayout = new SwipeBackLayout(this);
        rootLayout.attachToActivityAndAsRootLayout(this);
    }
    @Override
    public void startActivity(Intent intent) {
        super.startActivity(intent);
        overridePendingTransition(R.anim.base_slide_right_in, R.anim.base_slide_remain);
    }
    @Override
    public void onBackPressed() {
        super.onBackPressed();
        overridePendingTransition(0, R.anim.base_slide_right_out);
    }
    @Override
    public void finish() {
        super.finish();
        overridePendingTransition(0, R.anim.base_slide_right_out);
    }
}

MainActivity

public class MainActivity extends ListActivity {
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);
        String[] array = { "普通的Activity"//
                "普通的Activity:不顯示陰影",//
                "普通的Activity:自定義陰影"//
                "普通的Activity:背景透明",//
                "普通的Activity:Translucent主題",//
                "普通的Activity:Holo_Light主題",//
                "有ListView的Activity"//
                "當touch位置有ViewPager,但ViewPager不是在item0時,不攔截滑動事件",//
                "當touch位置有ViewPager,即便ViewPager不是在item0時,也攔截【從屏幕邊緣down】的滑動事件" };//我感受這種方式的用戶體驗更好
        setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1new ArrayList<String>(Arrays.asList(array))));
    }
    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        if (position <= 5) {
            Intent intent = new Intent(this, NormalActivity.class);
            intent.putExtra("position", position);
            startActivity(intent);
        } else if (position == 6) startActivity(new Intent(MainActivity.this, ListViewActivity.class));
        else {
            Intent intent = new Intent(this, ViewPagerActivity.class);
            intent.putExtra("position", position);
            startActivity(intent);
        }
        overridePendingTransition(R.anim.base_slide_right_in, R.anim.base_slide_remain);
    }
}

NormalActivity

public class NormalActivity extends SwipeBackActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //若是子類要requestWindowFeature,必須放在super.onCreate以前,由於父類SwipeBackActivity的onCreate方法已經獲取到了DecorView
        //也即子類在調用super.onCreate後,其窗口裝飾風格(即相應的根佈局文件)已經肯定好了(默認的),如:是否有標題、是否有icon等。
        //因爲要求窗口裝飾風格一經肯定就不能再修改,不然直接拋異常!而requestWindowFeature的做用就是根據你的設置選擇匹配的窗口裝飾風格
        //因此requestWindowFeature必須在包括setContentView以及getWindow().getDecorView()等行爲以前調用!
        //注意:以上規則只適合requestWindowFeature等方法,全屏設置能夠放在【任何】位置,好比在點擊某個View後調用也是能夠的
        requestWindowFeature(Window.FEATURE_LEFT_ICON);//實際上這行代碼沒任何意義,由於會被super.onCreate中的設置覆蓋掉
        super.onCreate(savedInstanceState);
        TextView tv_info = new TextView(this);
        tv_info.setTextColor(Color.BLACK);
        tv_info.setTextSize(TypedValue.COMPLEX_UNIT_SP, 25);
        tv_info.setBackgroundColor(Color.RED);//必須設置背景色,不然是透明的
        tv_info.setGravity(Gravity.CENTER);
        switch (getIntent().getIntExtra("position", 0)) {
        case 0:
            tv_info.setText("默認顯示指定的陰影\n對於普通的Activity,沒必要必定要在屏幕邊緣開始滑才能退出");
            break;
        case 1:
            tv_info.setText("不顯示陰影");
            rootLayout.setShowShadow(false);
            break;
        case 2:
            tv_info.setText("自定義陰影");
            rootLayout.setmShadowDrawable(getResources().getDrawable(R.drawable.ic_launcher));
            break;
        case 3:
            tv_info.setText("背景透明");
            tv_info.setBackgroundColor(Color.TRANSPARENT);
            break;
        case 4:
            tv_info.setText("Translucent主題");
            new AlertDialog.Builder(this).setTitle("Translucent主題").create().show();
            break;
        case 5:
            tv_info.setText("Holo_Light主題");
            setTheme(android.R.style.Theme_Holo_Light);
            new AlertDialog.Builder(this).setTitle("Holo_Light主題").create().show();
            break;
        }
        setContentView(tv_info);
    }
}

ListViewActivity

public class ListViewActivity extends SwipeBackActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        List<String> list = new ArrayList<String>();
        for (int i = 0; i <= 30; i++) {
            list.add("包含ListView時不會有手勢衝突");
        }
        ListView mListView = new ListView(this);
        mListView.setBackgroundColor(Color.GREEN);//設置背景色
        mListView.setAdapter(new ArrayAdapter<String>(ListViewActivity.this, android.R.layout.simple_list_item_1, list));
        setContentView(mListView);
    }
}

ViewPagerActivity

public class ViewPagerActivity extends SwipeBackActivity {
    private List<View> list = new ArrayList<View>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ViewPager viewPager = new ViewPager(this);
        for (int i = 0; i < 5; i++) {
            TextView tv_info = new TextView(this);
            tv_info.setTextColor(Color.RED);
            tv_info.setTextSize(TypedValue.COMPLEX_UNIT_SP, 300);
            tv_info.setBackgroundColor(Color.GREEN);//必須設置背景色,不然是透明的
            tv_info.setGravity(Gravity.CENTER);
            tv_info.setText("" + i);
            list.add(tv_info);
        }
        viewPager.setAdapter(new Adapter(thislist));
        setContentView(viewPager);
        if (getIntent().getIntExtra("position", 0) == 8) rootLayout.setInterceptWhenTouchViewPagerIfNotFirst(true);
    }
    public class Adapter extends PagerAdapter {
        private List<View> list;
        public Adapter(Context context, List<View> list) {
            this.list = list;
        }
        @Override
        public int getCount() {
            return list.size();
        }
        @Override
        public boolean isViewFromObject(View arg0, Object arg1) {
            return arg0 == arg1;
        }
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            container.removeView(list.get(position));
        }
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            View v = list.get(position);
            container.addView(v);
            return v;
        }
    }
}

清單文件

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.slidingfinish"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk
        android:minSdkVersion="17"
        android:targetSdkVersion="19" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.Translucent" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@android:style/Theme.Holo.Light" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".ListViewActivity" />
        <activity android:name=".NormalActivity" />
        <activity android:name=".ViewPagerActivity" />
    </application>
</manifest>

附件列表

相關文章
相關標籤/搜索