最近在使用IOS系统的时候,发现侧滑关闭很实用,因为单手就可以操作,不需要点击左上角的回退按钮、或者返回键了。

所以打算在android上实现这个技术。

需求:

1:IOS只能在屏幕边缘开始,往中间进行侧滑才能关闭;我们希望触发点可以在任意位置。

2:对现有代码入侵尽可能下,简单配置下就可以实现这个功能。

实战参考:请参考本人的博客园项目

参考了GitHub上一个开源框架,优化后形成现有的框架

下面是其实现原理,总结的很到位,做了部分修改

Android activity滑动返回原理

像fragment一样,activity本身是不可以滑动的,但是我们可以制造一个正在滑动activity的假象,使得看起来这个activity正在被手指滑动。

其原理其实很简单,我们滑动的其实是activity里面的可见view元素,而我们将activity设置为透明的,这样当view滑过的时候,由于activity的底部是透明的,我们就可以在滑动过程中看到下面的activity,这样看起来就是在滑动activity。

所以activity滑动效果分两步,1,设置透明,2,滑动view

设置透明: 很简单,建立一个Style,在Style里面添加下面两行并将这个style应用在activity上就可以了

<item name="android:windowBackground">@*android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>

先看看activity的层次结构:

我们用的activity的xml的根view并不是activity的根view,在它上面还有一个父view,id是android.R.id.content,再向上一层,还有一个view,它是一个LinearLayout,

它除了放置我们创建的view之外,还放置我们的xml之外的一些东西比如放ActionBar什么的。而再往上一级,就到了activity的根view——DecorView。

如下图

要做到像iOS那样可以滑动整个activity,只滑动我们在xml里面创建的view显然是不对的

因为我们还有ActionBar什么的,所以我们要滑动的应该是DecorView或者倒数第二层的那个view

而要滑动view的话,我们要重写其父窗口的onInterceptTouchEvent以及onTouchEvent【当然使用setOnTouchListener不是不可能,但是如果子view里面有一个消费了onTouch事件,那么也就接收不到了】,但是窗口的创建过程不是我们能控制的,DecorView的创建都不是我们能干预的。

解决办法就是,我们自己创建一个SwipeLayout,然后人为地插入顶层view中,放置在DecorView和其下面的LinearLayout中间,随着手指的滑动,不断改变SwipeLayout的子view——曾经是DecorView的子view——的位置

这样我们就可以控制activity的滑动啦。我们在activity的onPostCreate方法中调用swipeLayout.replaceLayer替换我们的SwipeLayout,代码如下

 /**
* 将本view注入到decorView的子view上
* 在{@link Activity#onPostCreate(Bundle)}里使用本方法注入
*/
public void injectWindow() {
if (mIsInjected)
return; final ViewGroup root = (ViewGroup) mActivity.getWindow().getDecorView();
mContent = root.getChildAt(0);
root.removeView(mContent);
this.addView(mContent, new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
root.addView(this);
mIsInjected = true;
}

然后我们把这些写成一个SwipeActivity,其它activity只要继承这个SwipeActivity就可以实现滑动返回功能(当然Style仍然要设置的) 这里只说滑动activity的原理,剩下的都是控制滑动的事了,详见代码

BTW,滑动Fragment原理其实一样,只不过更加简单,Fragment在view树中就是它inflate的元素,用fragment.getView可以取得,滑动fragment其实滑动的就是fragment.getView。只要把滑动方法写在它父view中就可以了

在实际使用中,我们发现,当你把Activity背景色设置为透明之后,原先设置的Activity进入、退出动画效果就消失了

原因是因为透明背景色、Translucent的Activity,它的动画体系和有背景色的Activity是不同的,看下面代码的parent部分

  <!-- 日间模式,透明 -->
<style name="AppTheme.day.transparent" parent="AppTheme.day">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">@style/transparentAnimation</item>
</style> <!--普通有底色的Activity动画-->
<style name="normalAnimation" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/slide_right_in</item>
<item name="android:activityOpenExitAnimation">@anim/slide_left_out</item>
<item name="android:activityCloseEnterAnimation">@anim/slide_left_in</item>
<item name="android:activityCloseExitAnimation">@anim/slide_right_out</item>
</style>
<!--透明的Activity动画-->
<style name="transparentAnimation" parent="@android:style/Animation.Translucent">
<item name="android:windowEnterAnimation">@anim/slide_right_in</item>
<item name="android:windowExitAnimation">@anim/slide_right_out</item>
</style>

其他也没啥好说的了,直接看代码吧

package zhexian.learn.cnblogs.ui;

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout; import zhexian.learn.cnblogs.R; /**
* 侧滑关闭的布局,使用方式
* 在目标容器的onCreate里面创建本布局 {@link #SwipeCloseLayout(Context)}
* 在目标容器的onPostCreate里面将本布局挂载到decorView下{@link #injectWindow()}
* Created by 陈俊杰 on 2016/2/16.
*/
public class SwipeCloseLayout extends FrameLayout {
private static final int ANIMATION_DURATION = 200; /**
* 是否可以滑动关闭页面
*/
private boolean mSwipeEnabled = true;
private boolean mIsAnimationFinished = true;
private boolean mCanSwipe = false;
private boolean mIgnoreSwipe = false;
private boolean mHasIgnoreFirstMove; private Activity mActivity;
private VelocityTracker tracker;
private ObjectAnimator mAnimator;
private Drawable mLeftShadow;
private View mContent;
private int mScreenWidth;
private int touchSlopLength;
private float mDownX;
private float mDownY;
private float mLastX;
private float mCurrentX;
private int mPullMaxLength;
private boolean mIsInjected; public SwipeCloseLayout(Context context) {
this(context, null, 0);
} public SwipeCloseLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} public SwipeCloseLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mActivity = (Activity) context;
mLeftShadow = context.getResources().getDrawable(R.drawable.left_shadow);
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
touchSlopLength = (int) (20 * displayMetrics.density);
touchSlopLength *= touchSlopLength;
mScreenWidth = displayMetrics.widthPixels;
mPullMaxLength = (int) (mScreenWidth * 0.33f);
setClickable(true);
} /**
* 将本view注入到decorView的子view上
* 在{@link Activity#onPostCreate(Bundle)}里使用本方法注入
*/
public void injectWindow() {
if (mIsInjected)
return; final ViewGroup root = (ViewGroup) mActivity.getWindow().getDecorView();
mContent = root.getChildAt(0);
root.removeView(mContent);
this.addView(mContent, new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
root.addView(this);
mIsInjected = true;
} public boolean isSwipeEnabled() {
return mSwipeEnabled;
} public void setSwipeEnabled(boolean swipeEnabled) {
this.mSwipeEnabled = swipeEnabled;
} @Override
protected boolean drawChild(@NonNull Canvas canvas, @NonNull View child, long drawingTime) {
boolean result = super.drawChild(canvas, child, drawingTime);
final int shadowWidth = mLeftShadow.getIntrinsicWidth();
int left = (int) (getContentX()) - shadowWidth;
mLeftShadow.setBounds(left, child.getTop(), left + shadowWidth, child.getBottom());
mLeftShadow.draw(canvas);
return result;
} @Override
public boolean dispatchTouchEvent(@NonNull MotionEvent ev) {
if (mSwipeEnabled && !mCanSwipe && !mIgnoreSwipe) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = ev.getX();
mDownY = ev.getY();
mCurrentX = mDownX;
mLastX = mDownX;
break;
case MotionEvent.ACTION_MOVE:
float dx = ev.getX() - mDownX;
float dy = ev.getY() - mDownY;
if (dx * dx + dy * dy > touchSlopLength) {
if (dy == 0f || Math.abs(dx / dy) > 1) {
mDownX = ev.getX();
mDownY = ev.getY();
mCurrentX = mDownX;
mLastX = mDownX;
mCanSwipe = true;
tracker = VelocityTracker.obtain();
return true;
} else {
mIgnoreSwipe = true;
}
}
break;
}
}
if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) {
mIgnoreSwipe = false;
}
return super.dispatchTouchEvent(ev);
} @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mCanSwipe || super.onInterceptTouchEvent(ev);
} @Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
if (mCanSwipe) {
tracker.addMovement(event);
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mDownX = event.getX();
mCurrentX = mDownX;
mLastX = mDownX;
break;
case MotionEvent.ACTION_MOVE:
mCurrentX = event.getX();
float dx = mCurrentX - mLastX;
if (dx != 0f && !mHasIgnoreFirstMove) {
mHasIgnoreFirstMove = true;
dx = dx / dx;
}
if (getContentX() + dx < 0) {
setContentX(0);
} else {
setContentX(getContentX() + dx);
}
mLastX = mCurrentX;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
tracker.computeCurrentVelocity(10000);
tracker.computeCurrentVelocity(1000, 20000);
mCanSwipe = false;
mHasIgnoreFirstMove = false;
int mv = mScreenWidth * 3;
if (Math.abs(tracker.getXVelocity()) > mv) {
animateFromVelocity(tracker.getXVelocity());
} else {
if (getContentX() > mPullMaxLength) {
animateFinish(false);
} else {
animateBack(false);
}
}
tracker.recycle();
break;
default:
break;
}
}
return super.onTouchEvent(event);
} public void cancelPotentialAnimation() {
if (mAnimator != null) {
mAnimator.removeAllListeners();
mAnimator.cancel();
}
} public float getContentX() {
return mContent.getX();
} private void setContentX(float x) {
mContent.setX(x);
invalidate();
} public boolean isAnimationFinished() {
return mIsAnimationFinished;
} /**
* 弹回,不关闭,因为left是0,所以setX和setTranslationX效果是一样的
*
* @param withVel 使用计算出来的时间
*/
private void animateBack(boolean withVel) {
cancelPotentialAnimation();
mAnimator = ObjectAnimator.ofFloat(this, "contentX", getContentX(), 0);
int tmpDuration = withVel ? ((int) (ANIMATION_DURATION * getContentX() / mScreenWidth)) : ANIMATION_DURATION;
if (tmpDuration < 100) {
tmpDuration = 100;
}
mAnimator.setDuration(tmpDuration);
mAnimator.setInterpolator(new DecelerateInterpolator());
mAnimator.start();
} private void animateFinish(boolean withVel) {
cancelPotentialAnimation();
mAnimator = ObjectAnimator.ofFloat(this, "contentX", getContentX(), mScreenWidth);
int tmpDuration = withVel ? ((int) (ANIMATION_DURATION * (mScreenWidth - getContentX()) / mScreenWidth)) : ANIMATION_DURATION;
if (tmpDuration < 100) {
tmpDuration = 100;
}
mAnimator.setDuration(tmpDuration);
mAnimator.setInterpolator(new DecelerateInterpolator());
mAnimator.addListener(new Animator.AnimatorListener() { @Override
public void onAnimationStart(Animator animation) {
mIsAnimationFinished = false;
} @Override
public void onAnimationRepeat(Animator animation) { } @Override
public void onAnimationEnd(Animator animation) {
mIsAnimationFinished = true;
if (!mActivity.isFinishing()) {
mActivity.finish();
}
} @Override
public void onAnimationCancel(Animator animation) {
mIsAnimationFinished = true;
}
});
mAnimator.start();
} private void animateFromVelocity(float v) {
int currentX = (int) getContentX();
if (v > 0) {
if (currentX < mPullMaxLength && v * ANIMATION_DURATION / 1000 + currentX < mPullMaxLength) {
animateBack(false);
} else {
animateFinish(true);
}
} else {
if (currentX > mPullMaxLength / 3 && v * ANIMATION_DURATION / 1000 + currentX > mPullMaxLength) {
animateFinish(false);
} else {
animateBack(true);
}
}
} public void finish() {
if (!isAnimationFinished()) {
cancelPotentialAnimation();
}
}
}

【android】侧滑关闭activity的更多相关文章

  1. 随手一写就是一个侧滑关闭activity

    刚忙完一段时间,今天刚清闲会,就把以前写的东西整理整理.于是冥冥中发现有些东西完全可以共享出来,毕竟那么常见,而且简单实用. 实现原因 其实侧滑关闭activity在网上也有大量的文章去介绍他,我也有 ...

  2. 侧滑关闭Activity的解决方案——SwipeBackLayout

    项目地址:ikew0ng/SwipeBackLayout: An Android library that help you to build app with swipe back gesture. ...

  3. 分分钟教你集成沉浸式侧滑关闭Activity

    网上搜索侧滑关闭Activity,几乎没有系统状态栏跟随页面一起联动的,有明显的撕裂感,而这里则是状态栏跟随页面联动的,说来集成也是简单,等会你就知道了. 个人习惯,写博客前喜欢先截图 1.首先以项目 ...

  4. 建立、配置和使用Activity——启动、关闭Activity

    一个Android应用通常都会包含多个Activity,但只有一个Activity会作为程序的入口——当该Android应用运行时将会自启动并执行该Activity.至于应用中的其他Activity, ...

  5. 【Android】12.2 利用Intent启动和关闭Activity

    分类:C#.Android.VS2015: 创建日期:2016-02-23 一.简介 Android应用程序中一般都有多个Activity,在Activity中,通过调用StartActivity方法 ...

  6. Android侧滑菜单代码实现

    前两天学习了hyman老师讲的Android侧滑菜单的实现,经过自己的整理分享出来给大家学习一下 现在很多APP都有菜单侧滑的功能,本篇文章主要讲解使用自定义的HorizontalScrollView ...

  7. Android 面试题--Activity

    1.什么是 Activity?Activity是Android组件中最基本也是最为常见用的四大组件(Activity,Service服务,Content Provider内容提供,BroadcastR ...

  8. Activity详解一 配置、启动和关闭activity

    先看效果图: Android为我们提供了四种应组件,分别为Activity.Service.Broadcast receivers和Content providers,这些组建也就是我们开发一个And ...

  9. Android 组件系列-----Activity保存状态

    本篇随笔将详细的讲解Activity保存状态的概念,也就是saving activity state. 一.Activity状态保持概念 保存Activity的状态是非常重要的,例如我们在玩一个游戏的 ...

随机推荐

  1. 学习调用WCF服务的各种方法

    1.开发工具调用WCF 这中方法很方便也很简单,很多工作VS就帮我们完成了.相信大家也不会对这种方法陌生.这里简单提一下.打开VS,在项目中添加服务引用: 在config中自动声明了有关服务的节点信息 ...

  2. VS2010 单文档+多视图+Outlook风格

    先来个段子 十年生死两茫茫,喜羊羊,灰太狼.舒克贝塔,蓝猫话凄凉.纵使相逢应不识,圣斗士,美猴王.老夫聊发少年狂,治肾亏,不含糖.锦帽貂裘,千骑用康王.为报倾城随太守,三百年,九芝堂.夜来幽梦忽还乡, ...

  3. mysql中出现Incorrect DECIMAL value: '0' for column '' at row -1错误解决方案

    本人开发项目时,在从一个服务器导出数据库到另一服务器时,存储过程中,报Incorrect DECIMAL value: '0' for column '' at row -1错误. 原因: 存储过程中 ...

  4. javascript 内部对象(1)——Math 对象

    Math是javascript中的内部对象之一,主要用于处理数学方面的任务,是一种静态对象.和其他动态对象如Date.String等不同的是它没有构造函数Math(),可以直接使用属性和方法. 例如使 ...

  5. 百度地图简单使用——添加折线,圆形等(html,js)

    地图覆盖物概述 所有叠加或覆盖到地图的内容,我们统称为地图覆盖物.如标注.矢量图形元素(包括:折线和多边形和圆).信息窗口等.覆盖物拥有自己的地理坐标,当您拖动或缩放地图时,它们会相应的移动. 地图A ...

  6. hdu 1561 The more, The Better(树形dp,基础)

    The more, The Better Time Limit: 6000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Oth ...

  7. HBase 专题技术收录

    HBase系列: 博客地址:http://www.cnblogs.com/panfeng412/tag/HBase/ 技术专题文章: HBase中MVCC的实现机制及应用情况 HBase在单Colum ...

  8. 2016开发一个app需要多少钱?app开发需要哪些成本-app开发问题汇总-广州达到信息

    作为一个APP开发从业者,被外行的朋友们问及最多的问题是,"做一个网站需要多少钱?"或者"开发一个APP需要多少钱?".作为开发过完整网站项目和手机APP的人, ...

  9. 【MVC 4】2.使用 Razor

    作者:[美]Adam Freeman      来源:<精通ASP.NET MVC 4> Razor 是微软 MVC3 引入的视图引擎的名称,并在MVC 4 中进行了修订.视图引擎处理 A ...

  10. 2014 Super Training #1 F Passage 概率DP

    原题: HDU 3366   http://acm.hdu.edu.cn/showproblem.php?pid=3366 本来用贪心去做,怎么都WA,后来看网上原来是一个DP题. 首先按P/Q来做排 ...