转载请标明出处:http://blog.csdn.net/android_ls/article/details/8756059

一、滑动效果的实现原理:

1、采用RelativeLayout作为父容器, 当调用addView(View child)方法向其中添加子View(子View采用FrameLayout),并且其子View的布局参数都设置的是填充整个父容器的大小(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT))。那么父容器中当前显示的应该是最后添加的View。

2、 要实现子View可以在父容器中滑动,那么我们就得重写父容器的onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)方法,拦截用户触屏手势并作出判断和事件处理。比如,当前用户的某个(单击、向左滑动和向右滑动等)触屏事件,是否需要响应,若要响应,是父容器自己去处理呢,还是应该交给父容器里的某个子View去处理。

3、要做到第2小点中提到的,必须先了解ViewGrop的onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)回调方法,在整个View树(应用框架)中的调用先后顺序及其返回值所代表的含义。下面做个小测试,前提:ViewGroup(父容器中的一个子View)有子View,并且子View中的View有事件处理器(比如,子View是Button,事件处理器指的就是Button的点击事件监听器中的onClick(View v)方法)或者子View可以获得焦点(比如选中效果)。

自定义类继承RelativeLayout类,如下:

public class ParentContainer extends RelativeLayout {private static final String TAG = "ParentContainer";public ParentContainer(Context context) {super(context);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {Log.e(TAG, "ParentContainer : onInterceptTouchEvent()");return super.onInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.e(TAG, "ParentContainer : onTouchEvent()");return super.onTouchEvent(event);}}

其子View代码如下:

public class ChildContainer extends FrameLayout {private static final String TAG = "ChildContainer";public ChildContainer(Context context) {super(context);Button btnTest = new Button(context);btnTest.setText("测试按钮");btnTest.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.i(TAG, "ChildContainer : 我响应了单击事件");}});LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);this.addView(btnTest, params);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {Log.i(TAG, "ChildContainer : onInterceptTouchEvent()");return super.onInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.i(TAG, "ChildContainer : onTouchEvent()");return super.onTouchEvent(event);}}

测试Activity代码:

public class TestActivity extends Activity {@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ParentContainer mSlideContainer = new ParentContainer(this);ChildContainer childContainer = new ChildContainer(this);LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);mSlideContainer.addView(childContainer, params);setContentView(mSlideContainer);}}

a. 默认情况下,单击子View中的Button按钮,LogCat打印Log如下:

修改父容器的onInterceptTouchEvent()返回值为false,单击子View的按钮,打印Log与默认值一样。跟踪源码发现其实默认返回值就是false。修改返回值为true,单击子View的按钮,LogCat打印Log如下:

结论:父容器中onInterceptTouchEvent()方法的返回值为true时,表示将事件交给ViewGroup自己的onTouchEvent()去处理;返回值为false时,表示将事件交给ViewGroup的子View的onInterceptTouchEvent()去处理。(默认的处理方式)
b. 父容器使用默认的值,修改子View的onTouchEvent()方法返回值为false,单击子View,LogCat打印Log如下:

修改子View的onTouchEvent()方法返回值为true,单击子View,LogCat打印Log如下:

结论:父容器使用默认的值,修改子View的onTouchEvent()方法返回值为false,表示将事件交父View处理;修改子View的onTouchEvent()方法返回值为true,表示该事件子View自己已经处理了,到这里终止。
4、在父容器中,拦截用户触屏手势后,想交给父容器自己去处理,或者是想交给父容器里的某个子View去处理,应该怎么实现,通过上面的讲解,我想大家已经明白了,决定事件的传递顺序或在那个View里终止传递,是通过ViewGroup中的onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)方法的返回值来决定的。接下来开始聊如何实现父容器中的子View的左右滑动(滚动),这里就用到了Scroller和VelocityTracker两个类。

a. 为什么要用Scroller类? 如果实现想把一个View偏移至指定坐标(x,y)处,利用View类提供的scrollTo()方法直接调用就可以了。但是View类的scrollTo()方法是非常迅速的将View从一个坐标点(20, 0)移到另一个坐标点(300, 0),而没有对这个偏移过程有任何控制,对用户而言这件事发生的很突然,用户体验不好。而Scroller类提供的startScroll()方法,在偏移过程中添加了动画,提升了用户体验。因此我们选择使用Scroller类的对象来实现View的偏移。

b. VelocityTracker类,主要用跟踪触摸屏事件(flinging事件和其他gestures手势事件)的速率。 用addMovement(MotionEvent)函数将Motion event加入到VelocityTracker类实例中。你可以使用getXVelocity() 或getXVelocity()获得横向和竖向的速率到速率时,但是使用它们之前请先调用computeCurrentVelocity(int)来初始化速率的单位 。

关于computeCurrentVelocity(int units, float maxVelocity) 方法的参数列表解释:

int unitis表示速率的基本时间单位。unitis值为1的表示是,一毫秒时间单位内运动了多少个像素, unitis值为1000表示一秒(1000毫秒)时间单位内运动了多少个像素。

float maxVelocity表示速率的最大值。

二、按上面的讲解思路编码实现:

1、滑动方式实现:

只在父容器的onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)方法中,添加MotionEvent.ACTION_MOVE的事件处理。
父容器类代码如下:

package com.everyone.android.widget;import android.content.Context;
import android.util.Log;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.widget.RelativeLayout;
import android.widget.Scroller;/*** 功能描述:手指在屏幕上左右滑动时,该类的实例负责其的子View的左右偏移(滚动)* @author android_ls*/
public class ScrollerContainer extends RelativeLayout {private static final String TAG = "ScrollerContainer";private Scroller mScroller;private VelocityTracker mVelocityTracker;/*** 手柄(手把)的宽度*/private int mHandlebarWidth;/*** 一秒时间内移动了多少个像素*/private float mVelocityValue;/*** 在偏移过程中,动画持续的时间*/private static final int ANIMATION_DURATION_TIME = 300;public ScrollerContainer(Context context) {super(context);mScroller = new Scroller(context);mHandlebarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, getResources().getDisplayMetrics());}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {Log.e(TAG, "ParentContainer : onInterceptTouchEvent()");mVelocityTracker = VelocityTracker.obtain();mVelocityTracker.addMovement(ev);switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:Log.i(TAG, "onInterceptTouchEvent():  ACTION_DOWN");break;case MotionEvent.ACTION_MOVE:Log.i(TAG, "onInterceptTouchEvent():  ACTION_MOVE");mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity());mVelocityValue = Math.abs(mVelocityTracker.getXVelocity()) ;Log.d(TAG, "onInterceptTouchEvent():  mVelocityValue = " + mVelocityValue);if (mVelocityValue > 300) {return true;}break;case MotionEvent.ACTION_UP:Log.i(TAG, "onInterceptTouchEvent():  ACTION_UP");break;case MotionEvent.ACTION_CANCEL:Log.i(TAG, "onInterceptTouchEvent():  ACTION_CANCEL");break;default:break;}return super.onInterceptTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.e(TAG, "ParentContainer : onTouchEvent()");float x = event.getX();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:Log.i(TAG, "onTouchEvent():  ACTION_DOWN");break;case MotionEvent.ACTION_MOVE:Log.i(TAG, "onTouchEvent():  ACTION_MOVE");getChildAt(1).scrollTo(-(int)x, 0);break;case MotionEvent.ACTION_UP:Log.i(TAG, "onTouchEvent():  ACTION_UP");float width = getWidth();float halfWidth = width / 2;Log.i(TAG, "onTouchEvent():  ACTION_UP x = " + x + "\t halfWidth = " + halfWidth);int scrollX = getChildAt(1).getScrollX();if ( x < halfWidth) {Log.i(TAG, "onTouchEvent():  ACTION_UP 向左滑动");mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME);invalidate();} else if ( x > halfWidth){Log.i(TAG, "onTouchEvent():  ACTION_UP 向右滑动");int toX = (int)(width - Math.abs(scrollX) - mHandlebarWidth);mScroller.startScroll(scrollX, 0, -toX, 0, ANIMATION_DURATION_TIME);invalidate();}break;case MotionEvent.ACTION_CANCEL:Log.i(TAG, "onTouchEvent():  ACTION_CANCEL");break;default:break;}return super.onTouchEvent(event);}@Overridepublic void computeScroll() {// super.computeScroll();if(mScroller.computeScrollOffset()){this.getChildAt(1).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());this.postInvalidate();}}}

子容器就是两个继承自FrameLayout的Layout,源码就不贴了。

测试类代码如下:

package com.everyone.android.ui;import android.app.Activity;
import android.os.Bundle;
import android.view.ViewGroup.LayoutParams;import com.everyone.android.widget.FreshNewsLayout;
import com.everyone.android.widget.LeftPanelLayout;
import com.everyone.android.widget.ScrollerContainer;public class TestActivity extends Activity {/*** 左侧面板*/private LeftPanelLayout mLeftPanelLayout;/*** 新鲜事*/private FreshNewsLayout mFreshNewsLayout;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);ScrollerContainer mSlideContainer = new ScrollerContainer(this);LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);mLeftPanelLayout = new LeftPanelLayout(this.getApplicationContext());mFreshNewsLayout = new FreshNewsLayout(this.getApplicationContext());mSlideContainer.addView(mLeftPanelLayout, params);mSlideContainer.addView(mFreshNewsLayout, params);setContentView(mSlideContainer);}}

运行效果图如下:

2、单击方式实现:

左右滑动实现子View的滚动,有一个临界值,一秒时间内移动了的像素数要大于某个预设的值,才会触动相应事件处理器。那么为了用户体验好点,我们提供另外一种操作方式,那就是单击事件。假设ViewGroup中有两个子View A和B,B处于A上面,两个子View是叠在一起的。默认显示的是B,并占据着整个手机屏幕,A是看不见的。为了能看见A,并且可以操作,我们需要把B视图(子View)移动一定单位,可以在B视图中添加子View(Button)并绑定事件监听器。当用户点击B视图中的特定子View(Button)时,让B视图偏移一定单位,我么就能看见A视图;当B处于A上并偏移了一定的单位,这时单击B,实现B视图移动到回去(恢复默认显示)。

a. 单击B,实现B视图移动到回去(恢复默认显示),代码如下:

    @Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {Log.e(TAG, "ParentContainer : onInterceptTouchEvent()");mVelocityTracker = VelocityTracker.obtain();mVelocityTracker.addMovement(ev);switch (ev.getAction()) {case MotionEvent.ACTION_DOWN:Log.i(TAG, "onInterceptTouchEvent():  ACTION_DOWN");int x = (int) ev.getX();int width = getWidth();if(x >= (width - mHandlebarWidth)){isClick = true;}break;case MotionEvent.ACTION_MOVE:Log.i(TAG, "onInterceptTouchEvent():  ACTION_MOVE");mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity());mVelocityValue = Math.abs(mVelocityTracker.getXVelocity()) ;Log.d(TAG, "onInterceptTouchEvent():  mVelocityValue = " + mVelocityValue);if (mVelocityValue > 300) {return true;}break;case MotionEvent.ACTION_UP:Log.i(TAG, "onInterceptTouchEvent():  ACTION_UP");if (isClick) {isClick = false;int scrollX = getChildAt(1).getScrollX();mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME);                                                        invalidate();}break;case MotionEvent.ACTION_CANCEL:Log.i(TAG, "onInterceptTouchEvent():  ACTION_CANCEL");break;default:break;}return super.onInterceptTouchEvent(ev);}

b. 当用户点击B视图中的特定子View(Button)时,让B视图偏移一定单位。在父容器中添加滑动事件监听器和向右滑动的实现,代码如下:

  /*** 向右滑动View,让左侧操作面饭可见*/public void slideToRight() {float width = getWidth();int scrollX = getChildAt(1).getScrollX();int toX = (int)(width - Math.abs(scrollX) - mHandlebarWidth);mScroller.startScroll(scrollX, 0, -toX, 0, ANIMATION_DURATION_TIME);invalidate();}/*** View滑动事件监听器* @author android_ls*/public interface OnSlideListener {/*** 向做滑动View*/public abstract void toLeft();/*** 向右滑动View*/public abstract void toRight();}

子视图FreshNewsLayout的源码:

package com.everyone.android.widget;import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;import com.everyone.android.R;
import com.everyone.android.widget.ScrollerContainer.OnSlideListener;/*** 功能描述:新鲜事视图* @author android_ls*/
public class FreshNewsLayout extends FrameLayout {public LinearLayout llBack;private OnSlideListener mOnSlideListener;public FreshNewsLayout(Context context) {super(context);setupViews();}public FreshNewsLayout(Context context, AttributeSet attrs) {super(context, attrs);setupViews();}public void setOnSlideListener(OnSlideListener onSlideListener) {mOnSlideListener = onSlideListener;}private void setupViews() {final LayoutInflater mLayoutInflater = LayoutInflater.from(getContext());LinearLayout rlTopNavbar = (LinearLayout) mLayoutInflater.inflate(R.layout.fresh_news, null);addView(rlTopNavbar);llBack = (LinearLayout) rlTopNavbar.findViewById(R.id.ll_back);llBack.setOnClickListener(new OnClickListener() {public void onClick(View v) {if (mOnSlideListener != null) {mOnSlideListener.toRight();}}});}}

测试类代码如下:

package com.everyone.android.ui;import android.app.Activity;
import android.os.Bundle;
import android.view.ViewGroup.LayoutParams;import com.everyone.android.widget.FreshNewsLayout;
import com.everyone.android.widget.LeftPanelLayout;
import com.everyone.android.widget.ScrollerContainer;
import com.everyone.android.widget.ScrollerContainer.OnSlideListener;public class TestActivity extends Activity implements OnSlideListener {/*** 左侧面板*/private LeftPanelLayout mLeftPanelLayout;/*** 新鲜事*/private FreshNewsLayout mFreshNewsLayout;/*** 滚动(滑动)容器*/private ScrollerContainer mSlideContainer;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mSlideContainer = new ScrollerContainer(this);LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);mLeftPanelLayout = new LeftPanelLayout(this.getApplicationContext());mFreshNewsLayout = new FreshNewsLayout(this.getApplicationContext());mFreshNewsLayout.setOnSlideListener(this);mSlideContainer.addView(mLeftPanelLayout, params);mSlideContainer.addView(mFreshNewsLayout, params);setContentView(mSlideContainer);}@Overridepublic void toLeft() {// TODO Auto-generated method stub}@Overridepublic void toRight() {mSlideContainer.slideToRight();}}

Android仿人人客户端(v5.7.1)——采用RelativeLayout做父容器,实现左侧滑动菜单(一)...相关推荐

  1. Android仿人人客户端(v5.7.1)——采用RelativeLayout做父容器,实现左侧滑动菜单(二)...

    转载请标明出处:http://blog.csdn.net/android_ls/article/details/8758943 上一篇在Android仿人人客户端(v5.7.1)--应用主界面之滑动效 ...

  2. Android仿人人客户端(v5.7.1)——采用RelativeLayout做父容器,实现左侧滑动菜单(二)

    转载请标明出处:http://blog.csdn.net/android_ls/article/details/8758943 上一篇在Android仿人人客户端(v5.7.1)--应用主界面之滑动效 ...

  3. Android仿人人客户端(v5.7.1)——采用ViewGroup做父容器,实现左侧滑动菜单(三)...

    转载请标明出处:http://blog.csdn.net/android_ls/article/details/8761410 前面已实现以滑动的方式显示或隐藏左侧菜单,采用的父容器是自定义类继承自R ...

  4. Android仿人人客户端(v5.7.1)——项目框架新做的调整描述(项目中基类java源码)...

    转载请标明出处:http://blog.csdn.net/android_ls/article/details/8909068 声明:没看过仿人人android客户端系列博文,前面的相关文章的朋友,请 ...

  5. Android仿人人客户端(v5.7.1)——对从服务器端(网络)获取的图片进行本地双缓存处理(编码实现)...

    转载请标明出处:http://blog.csdn.net/android_ls/article/details/8797740 这篇是基于上一篇Android仿人人客户端(v5.7.1)--对从服务器 ...

  6. Android仿人人客户端(v5.7.1)——通过HTTPS协议的POST方式获取用户的基本信息

    转载请标明出处:http://blog.csdn.net/android_ls/article/details/8770537 一.扩展之前的网络模块 基于Android仿人人客户端(v5.7.1)- ...

  7. Android仿人人客户端(v5.7.1)——网络模块处理的架构

    转载请标明出处:http://blog.csdn.net/android_ls/article/details/8732427 声明:仿人人项目,所用所有图片资源都来源于官方人人android客户端, ...

  8. Android仿人人客户端(v5.7.1)——新鲜事之下拉列表(过滤项列表)

    转载请标明出处:http://blog.csdn.net/android_ls/article/details/8884335 声明:仿人人项目,所用所有图片资源都来源于其它Android移动应用,编 ...

  9. Android仿人人客户端(v5.7.1)——点击左侧菜单栏中的Item切换视图

    转载请标明出处:http://blog.csdn.net/android_ls/article/details/8765193 在前面几讲中,左侧菜单(左侧面板).满足滑动或点击子View的方式,打开 ...

最新文章

  1. 【转】什么是“对用户友好”
  2. 分析Cocos2d-x横版ACT手游源码 1、公共
  3. 中国电信:回归根本的SOC发展之道
  4. 2.10 词嵌入除偏-深度学习第五课《序列模型》-Stanford吴恩达教授
  5. wxWidgets:源代码概览
  6. C++有哪些性质(面向对象特点)
  7. 典型PC系统各操作指令执行时间
  8. spring事务三大接口
  9. tt公路车Java配置怎么样_普通公路车换TT车把可以吗,别的东西还有需要换的吗?...
  10. java 判断当前时间是否为节假日_浅谈Java8日期时间处理
  11. 求整数 在二进制表示中有多少个1的方法
  12. 4 构建Mysql+heartbeat+DRBD+LVS集群应用系统系列之Lvs为Mysql-slave做负载均衡
  13. android ps icon图标制作,PS设计App图标教程
  14. u盘写保护怎么才能真正去掉
  15. 300ETF期权和50ETF期权的区别
  16. EXCEL如何批量调整图片大小?
  17. Mybatis-9.28
  18. Android设置系统时间和时区
  19. 再谈异常处理try-catch-finally
  20. 计算机大专生的平均工资水平,大学/大专应届毕业生工资待遇分析报告 - 职业圈...

热门文章

  1. 华为机试HJ62:查找输入整数二进制中1的个数
  2. mybatisplus service insert 空指针_c++ 图解层序遍历和逐层打印智能指针建造的二叉树...
  3. java gui设置圆形按钮_简单的设置圆形按钮
  4. Shell解析curl返回的json数据
  5. elasticsearch报错exceptions.RequestError(400, u'mapper_parsing_exception', u'No handler field..
  6. 亲密关系沟通-【独特性】尊重与探索他人
  7. python nonlocal的用法_python global和nonlocal用法解析
  8. python 识别图形验证码_Python图片验证码降噪处理实例!此乃识别验证码神技!...
  9. qt 打开html文件怎么打开文件夹路径,QT打开网页
  10. google支付接入PHP语言,PHP语言开发Paypal支付demo的具体实现