【Android】Activity右滑返回的实现

转载请注明出处:http://blog.csdn.net/h28496/article/details/49227419

1. 滑动返回的效果

 
效果描述:

  1. 从左侧向右滑动将当前Activity向右移动,并显示出下方的Activity。
  2. 在移动的过程中,透明部分有透明度的变化。

2. 如何使得Activity滑动?

1. 要滑动的是什么?

我们要滑动的是整个Activity的视图。不太清楚Activity视图结构的可以看一下下面这张图: 

一般在代码中通过setContentView(View view)把view放到了android.R.id.content对应的FrameLayout中。 
与该FrameLayout平级的还有它下面那些View,比如ActionBar等。 
如果我们要使得整个Activity的界面滑动,就需要使得根布局decorView滑动。 
通过在Activity中用如下代码可以获得decorView:

View decorView = getWindow().getDecorView();
 
 
  • 1

2. 具体怎么滑动?

  1. 当手指按下时,获得按下时的坐标(xDown, yDown)
  2. 手指移动时,获得当前手指坐标(xCurrent, yCurrent),可以得到水平移动距离 distanceX = xCurrent - xDown
  3. 获得水平移动距离之后,通过setX()方法设置界面的X坐标。

3. 如何获得手指的位置?

在Activity的onTouchEvent(MotionEvent event)方法中即可获得手指的位置。 
event.getX() 为手指的X坐标; 
event.getY() 为手指的Y坐标。

4. 抬起手指后的行为

手指抬起时,应该恢复到初始位置还是结束当前Activity呢?这个可以随意设置了。假设我们以屏幕的一半为界限。 
1. 当滑动超过一半时,松开手后,界面继续向右侧滑动直到完全在屏幕上不可见,这时调用finish()。 
2. 当滑动没有超过一半时,松开手后,界面返回到初始状态,即x坐标为0。

3. 一个简单的滑动返回效果

效果描述:

FirstActivity 的布局是 layout_first.xml 
SecondActivity 的布局是 layout_second.xml 
FirstActivity 中有一个按钮,点击跳转到 SecondActivity 
在 SecondActivity 右滑返回到 FirstActivity


1. 布局文件: layout_first.xml (背景蓝色)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#8888ff"
    tools:context="${relativePackage}.${activityClass}" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="跳转到SecondActivity" />

</RelativeLayout>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

2. 布局文件: layout_second.xml (背景红色)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff8888"
    tools:context="${relativePackage}.${activityClass}" >

</RelativeLayout>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

3. 代码:Activity_First.java

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;

public class FirstActivity extends Activity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_first);
    }

    public void nextActivity(View v) {
        startActivity(new Intent(this, SecondActivity.class));
    }

}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

4. 代码:Activity_Second.java

注释有点多。

import android.app.Activity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;

/**
 * @author 郑海鹏
 * @since 2015年10月16日
 */
public class SecondActivity extends Activity {
    /**
     * 整个Activity视图的根视图
     */
    View decorView;

    /**
     * 手指按下时的坐标
     */
    float downX, downY;

    /**
     * 手机屏幕的宽度和高度 
     */
    float screenWidth, screenHeight;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // setContentView(View view)方法会把view放到bgLayout中。
        setContentView(R.layout.layout_second);

        // 获得decorView
        decorView = getWindow().getDecorView();

        // 获得手机屏幕的宽度和高度,单位像素
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        screenWidth = metrics.widthPixels;
        screenHeight = metrics.heightPixels;
    }

    /**
     * 通过重写该方法,对触摸事件进行处理
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(event.getAction() == MotionEvent.ACTION_DOWN){// 当按下时
            // 获得按下时的X坐标
            downX = event.getX();

        }else if(event.getAction() == MotionEvent.ACTION_MOVE){// 当手指滑动时
            // 获得滑过的距离
            float moveDistanceX = event.getX() - downX;
            if(moveDistanceX > 0){// 如果是向右滑动
                decorView.setX(moveDistanceX); // 设置界面的X到滑动到的位置
            }

        }else if(event.getAction() == MotionEvent.ACTION_UP){// 当抬起手指时
            // 获得滑过的距离
            float moveDistanceX = event.getX() - downX;
            if(moveDistanceX > screenWidth / 2){
                 // 如果滑动的距离超过了手机屏幕的一半, 结束当前Activity
                finish();
            }else{ // 如果滑动距离没有超过一半
                // 恢复初始状态
                decorView.setX(0);
            }
        }
        return super.onTouchEvent(event);
    }

}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

5. 效果展示一

 
可以看出,SecondActivity是随着我们的手指在滑动的,但是FirstActivity却并没有在SecondActivity滑动时显示出来。 
原因是:SecondActivity的背景不是透明的。

6. 背景透明

用主题可以使得Activity的背景也是透明的。 
首先自定义一个style,在res/values/styles.xml文件中(如果没有自己建一个)加上一个主题:

<style name="MyTheme" parent="@android:style/Theme.Black.NoTitleBar.Fullscreen">
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:colorBackgroundCacheHint">@null</item>
    <item name="android:windowIsTranslucent">true</item>
</style>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

并且在AndroidManifest.xml中,为我们的SecondActivity设置刚才的主题:

<activity
    android:name="zhp.test.decorviewtest.SecondActivity"
    android:theme="@style/MyTheme" >
</activity>
 
 
  • 1
  • 2
  • 3
  • 4

再次运行就得到了下面的效果了: 

3. 带有动画效果的滑动返回

细心的读者应该发现了,上面滑动返回的效果是很生硬的。 松手之后,如果滑动距离小于屏幕一半的宽度直接回到原始位置。同样,超过一半时,直接结束了。 
我们可以为decorView设置一个动画,让它在回复初始位置时,缓慢地滑动回去,在结束时,也滑动处屏幕后再结束当前Activity。关于属性动画,参见我的博客:【安卓】属性动画

1. 改进后的SecondActivity.java

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.Activity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;

/**
 * @author 郑海鹏
 * @since 2015年10月16日
 */
public class SecondActivity extends Activity {
    View decorView;
    float downX, downY;
    float screenWidth, screenHeight;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // setContentView(View view)方法会把view放到bgLayout中。
        setContentView(R.layout.layout_second);

        // 获得decorView
        decorView = getWindow().getDecorView();

        // 获得手机屏幕的宽度和高度,单位像素
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        screenWidth = metrics.widthPixels;
        screenHeight = metrics.heightPixels;
    }

    /**
     * 通过重写该方法,对触摸事件进行处理
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if(event.getAction() == MotionEvent.ACTION_DOWN){
            downX = event.getX();

        }else if(event.getAction() == MotionEvent.ACTION_MOVE){
            float moveDistanceX = event.getX() - downX;
            if(moveDistanceX > 0){
                decorView.setX(moveDistanceX);
            }

        }else if(event.getAction() == MotionEvent.ACTION_UP){
            float moveDistanceX = event.getX() - downX;
            if(moveDistanceX > screenWidth / 2){
                 // 如果滑动的距离超过了手机屏幕的一半, 滑动处屏幕后再结束当前Activity
                continueMove(moveDistanceX);
            }else{ 
                // 如果滑动距离没有超过一半, 往回滑动
                rebackToLeft(moveDistanceX);
            }
        }
        return super.onTouchEvent(event);
    }

    /**
     * 从当前位置一直往右滑动到消失。
     * 这里使用了属性动画。
     */
    private void continueMove(float moveDistanceX){
        // 从当前位置移动到右侧。
        ValueAnimator anim = ValueAnimator.ofFloat(moveDistanceX, screenWidth);
        anim.setDuration(1000); // 一秒的时间结束, 为了简单这里固定为1秒
        anim.start();

        anim.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // 位移
                float x = (float) (animation.getAnimatedValue());
                decorView.setX(x); 
            }
        });

        // 动画结束时结束当前Activity
        anim.addListener(new AnimatorListenerAdapter() {

            @Override
            public void onAnimationEnd(Animator animation) {
                finish();
            }

        });
    }

    /**
     * Activity被滑动到中途时,滑回去~
     */
    private void rebackToLeft(float moveDistanceX){
        ObjectAnimator.ofFloat(decorView, "X", moveDistanceX, 0).setDuration(300).start();  
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101

2. 效果展示三

5. 总结

1. 实现滑动返回的步骤

  1. 重写onTouchEvent(MotionEvent event)方法,使得Activity可以随着手指的滑动而滑动
  2. 为Activity设置一个透明背景的主题
  3. 为了更好的效果,我们可以为滑动添加一些动画效果

2. 其他效果和扩展

  • 在示例代码中,用到的主题是继承自@android:style/Theme.Black.NoTitleBar.Fullscreen的,读者也可以根据自己的需要继承自其他主题,还可以添加其他的item。 
    例如,下面这个主题就是用于滑动返回+Toolbar+沉浸式状态栏的, 也就是文章开始的示例图用到的主题:
<style name="ToolBarAndTranslucent" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowTranslucentStatus">true</item>
    <item name="android:windowTranslucentNavigation">true</item>
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:colorBackgroundCacheHint">@null</item>
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowContentOverlay">@null</item>
    <item name="android:listDivider">@drawable/divider</item>  
</style>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 读者可以把SecondActivity单独封装好,以后使用时直接继承该类就可以了实现滑动返回了。

  • 例子中并没有实现阴影效果,因为阴影效果有多种实现方式。读者可以自己实现。如果要实现文章开始的那种阴影,那我们移动的就不应该是decorView了,而是android.R.id.content对应的FrameLayout。并且对它父布局的背景色随着滑动的距离进行渐变。

3. 存在的问题

如果读者自己去实现该类,并在里面添加了一个ListView,或者ScrollView等可以滑动的视图。就有可能出现ListView可以滑动,但是我们的Activity并不能滑动返回了。 
这是一个关于View的滑动冲突的问题,下一篇文章中,我们将一起研究View的事件分发机制。

转载请注明出处:http://blog.csdn.net/h28496/article/details/49227419

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值