【Android】Activity右滑返回的实现

孤者浪人 提交于 2019-12-02 12:15:52

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();

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>

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>

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));
    }

}

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);
    }

}

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>

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

<activity
    android:name="zhp.test.decorviewtest.SecondActivity"
    android:theme="@style/MyTheme" >
</activity>

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

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();  
    }
}

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>
  • 读者可以把SecondActivity单独封装好,以后使用时直接继承该类就可以了实现滑动返回了。

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

3. 存在的问题

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

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

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