基本思想
我们的滑动逻辑主要是利用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 downX, touchX;
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(mViewPagers, this);//【递归】遍历整个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_1, new 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(this, list));
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>
附件列表
- 全新的手势,侧滑返回、全局右滑返回都OUT啦!
前言 Android快速开发框架-ZBLibrary 最近将以前的 全局右滑返回 手势功能改成了 底部左右滑动手势. 为什么呢?为了解决滑动返回手势的问题. 目前有3种滑动返回手势 一.侧滑返回 代表 ...
- iOS页面右滑返回的实现方法总结
1.边缘触发的系统方法 ①系统返回按钮 self.navigationController.interactivePopGestureRecognizer.enabled = YES; ②自定义返回 ...
- SwipeBackLayout的使用方法,右滑返回
使用方法: 需要右滑返回的activity继承baseActivity就可以, 如: public class SettingActivity extends BaseActivity {} 为防止滑 ...
- iOS开发之自定义导航栏返回按钮右滑返回手势失效的解决
我相信针对每一个iOS开发者来说~除了根视图控制器外~所有的界面通过导航栏push过去的界面都是可以通过右滑来返回上一个界面~其实~在很多应用和APP中~用户已经习惯了这个功能~然而~作为开发者的我们 ...
- App开发流程之右滑返回手势功能续
上一篇记录了利用系统私有变量和方法实现右滑返回手势功能:http://www.cnblogs.com/ALongWay/p/5893515.html 这篇继续记录另一种方案:利用UINavigatio ...
- App开发流程之右滑返回手势功能
iOS7以后,导航控制器,自带了从屏幕左边缘右滑返回的手势功能. 但是,如果自定义了导航栏返回按钮,这项功能就失效了,需要自行实现.又如果需要修改手势触发范围,还是需要自行实现. 广泛应用的一种实现方 ...
- 想要隐藏navigationBar,同时又想支持右滑返回功能
如果直接设置 self.navigationBarHidden = YES; 那同时也会屏蔽右滑返回功能. 解决办法1: self.navigationBarHidden = NO; self.nav ...
- iOS系统右滑返回全局控制方案
前言 今天有个小需求,在点击导航条上的返回按钮之前要调用某个API,并弹出UIAlertView来显示,根据用户的选项判断是否是返回还是继续留在当前控制器.举个简单的例子,当点击导航条上的左上角返回按 ...
- 第二十六篇、因为自定item(nav)而使系统右滑返回手势失效的解决方法
@interface ViewController () <uigesturerecognizerdelegate> @end@implementation ViewController ...
随机推荐
- 深度学习基础系列(九)| Dropout VS Batch Normalization? 是时候放弃Dropout了
Dropout是过去几年非常流行的正则化技术,可有效防止过拟合的发生.但从深度学习的发展趋势看,Batch Normalizaton(简称BN)正在逐步取代Dropout技术,特别是在卷积层.本文将首 ...
- 基于TensorFlow Serving的深度学习在线预估
一.前言 随着深度学习在图像.语言.广告点击率预估等各个领域不断发展,很多团队开始探索深度学习技术在业务层面的实践与应用.而在广告CTR预估方面,新模型也是层出不穷: Wide and Deep[1] ...
- HDU Today hdu 2112
题目:http://acm.hdu.edu.cn/showproblem.php?pid=2112 文章末有一些相应的测试数据供参考. 此题就是一个求最短路的问题,只不过现在的顶点名称变成了字符串而不 ...
- 使用DNSPod域名解析
1 在GoDaddy域名注册商 注册域名 https://sg.godaddy.com/zh/ 2 登陆DNSPod https://www.dnspod.cn 3 选择域名解析 添加域名 4 添加记 ...
- Highmaps网页图表教程之下载Highmaps与Highmaps的地图类型
Highmaps网页图表教程之下载Highmaps与Highmaps的地图类型 认识Highmaps Highmaps是Highcharts的姊妹框架,用来实现地图图表.它完全使用Javascript ...
- win32创建窗口函数(windows程序内部运行机制)
利用win32创建窗口函数,主要操作步骤为: 1.设计一个窗口类 2.注册窗口类 3.创建窗口 4.显示及窗口更新 5.消息循环 6.窗口过程函数 (1)设计一个窗口类 设计窗口类,这样的类型已经 ...
- Winform 串口通讯之读卡器
老板给我的第一个硬件就是一个读卡器, 说让我做一下试试,于是从网上查了查就写了出来,相当的简单. 但是后来还有一个地磅的串口通讯,我整整搞了一天. 在窗体类的构造函数中写入 Form.CheckFor ...
- luoguP3185 [HNOI2007]分裂游戏 枚举 + 博弈论
每个位置的瓶子中的每个石子是一个独立的游戏 只要计算出他们的\(sg\)值即可 至于方案数,反正不多\(n^3\)暴力枚举即可 反正怎么暴力都能过啊 复杂度\(O(Tn^3)\) #include & ...
- 【二分】【动态规划】Gym - 101156E - Longest Increasing Subsequences
求最长上升子序列方案数. 转载自:http://blog.csdn.net/u013445530/article/details/47958617,如造成不便,请博主联系我. 数组A包含N个整数(可能 ...
- 关于django Class-based views的理解
django是mvt模式,其中v就是这个显示逻辑部分,简单来讲,view函数可以说是接收request,然后处理,返回response的主体函数. 对于一些简单的逻辑关系,可以用直接用函数模式来进行处 ...