Android仿人人客户端(v5.7.1)——采用RelativeLayout做父容器,实现左侧滑动菜单(二)
2013-04-05 00:09
561 查看
转载请标明出处:/article/7738561.html
上一篇在Android仿人人客户端(v5.7.1)——应用主界面之滑动效果(一)中,滑动式菜单的初步效果已实现,这篇继续完善和优化。打个比方:就拿盖房子来说,上一篇完成的只是毛胚房,这篇要做的就是对毛胚房进行精美装修,之后才可以使用。好了,进入正题,假设ViewGroup中有两个子View
A和B,B处于A上面,两个子View是叠在一起的。默认显示的是B,并占据着整个手机屏幕,A是看不见的。为了能看见A,并且可以操作,我们需要把B视图(子View)移动一定单位,当A、B均可见并都可以接受事件。此时B视图可见部分就是手柄的宽度。
存在的问题:
1、当长按B视图的可见部分(手柄),会发现会触发A视图的事件,这显然是不行的。用户明明在B视图上操作,响应的却是A视图中的子View。
2、当在B视图的可见部分(手柄)上,用手指向右滑动时,发现B是还可以向右滑动。
3、当B视图占据着整个手机屏幕时,在B视图的任何区域水平滑动都可以让B向右滑动,也就说触控区域太大,会引起误操作。
4、手指水平滑动,响应很迟钝,灵敏度问题。
解决办法:
添加触摸事件分发处理回调方法dispatchTouchEvent(MotionEvent ev),其返回值决定事件的分发给谁处理。当用户手指在屏幕上按下后,阅读下面代码片段:
当前手指按下的坐标x值 ,是在手柄宽度范围内,改变标识值,返回true。表示onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)中的MotionEvent.ACTION_DOWN事件不再处理。
上一个Action down事件处理完后,接下来响应dispatchTouchEvent(MotionEvent ev)的MotionEvent.ACTION_MOVE,代码如下:
在MotionEvent.ACTION_MOVE代码块中,发现条件是满足的,返回true。表示和MotionEvent.ACTION_DOWN的一样,在后续的回调方法内Action move将不再处理。最后看当手指抬起,处理的代码如下:
上面的代码片段含义:当用户手指在屏幕上按下,当前ViewGroup中没有子View正在滚动,左侧面板处于可见,手指按下的坐标X值是在手柄(B视图的当前可见部分)的宽度范围内,则down事件到此处理结束。在move代码块里同样判断用户当前手指按下的坐标X值是在手柄宽度范围内的,则move事件也到此结束。在手指抬起后,up事件里判断在down是事件里设置的标识,发现条件都满足,响应用户在手柄上的单击事件请求。也就是说父View把用户的请求已响应了,不用传递到子View。子View也不知道发生过这件事。(大概意思就是这样)
触控区域太大的问题,每次都判断用户手指按下的坐标x值,是否在手柄宽度范围内(不管左侧面板是否可见)。灵敏度的问题,其实就是响应滚动子View的临界值的大小问题,值越小灵敏都越高。
ScrollerContainer类,修改后的代码如下:
主应用界面源码:
AppBaseActivity类的修改部分,在onCreate里添加的处理:
有关滑动菜单的到这里就完了,后面在使用过程中遇到什么问题,再处理。
看下效果图,还和上一篇一样,静态的图片看不出来优化后的效果,不过还是上传几张,有图有真相。
向右滑动或点击顶部箭头后
上一篇在Android仿人人客户端(v5.7.1)——应用主界面之滑动效果(一)中,滑动式菜单的初步效果已实现,这篇继续完善和优化。打个比方:就拿盖房子来说,上一篇完成的只是毛胚房,这篇要做的就是对毛胚房进行精美装修,之后才可以使用。好了,进入正题,假设ViewGroup中有两个子View
A和B,B处于A上面,两个子View是叠在一起的。默认显示的是B,并占据着整个手机屏幕,A是看不见的。为了能看见A,并且可以操作,我们需要把B视图(子View)移动一定单位,当A、B均可见并都可以接受事件。此时B视图可见部分就是手柄的宽度。
存在的问题:
1、当长按B视图的可见部分(手柄),会发现会触发A视图的事件,这显然是不行的。用户明明在B视图上操作,响应的却是A视图中的子View。
2、当在B视图的可见部分(手柄)上,用手指向右滑动时,发现B是还可以向右滑动。
3、当B视图占据着整个手机屏幕时,在B视图的任何区域水平滑动都可以让B向右滑动,也就说触控区域太大,会引起误操作。
4、手指水平滑动,响应很迟钝,灵敏度问题。
解决办法:
添加触摸事件分发处理回调方法dispatchTouchEvent(MotionEvent ev),其返回值决定事件的分发给谁处理。当用户手指在屏幕上按下后,阅读下面代码片段:
case MotionEvent.ACTION_DOWN: Log.i(TAG, "dispatchTouchEvent(): ACTION_DOWN"); mFinished = mScroller.isFinished(); if(mFinished){ int x = (int) ev.getX(); int width = getWidth(); if(mPanelInvisible)// 左侧面板可见 { if(x > (width - mHandlebarWidth)){ // 当前手指按下的坐标x值 ,是在手柄宽度范围内 isClick = true; mAllowScroll = true; return true; } else { isClick = false; mAllowScroll = false; } } else { // 左侧面板不可见 if(x < mHandlebarWidth ){ // 当前手指按下的坐标x值 < 手柄宽度 (也就是说在手柄宽度范围内,是可以相应用户的向右滑动手势) mAllowScroll = true; }else{ mAllowScroll = false; } } } else { // 当前正在滚动子View,其它的事不响应 return false; } break;
当前手指按下的坐标x值 ,是在手柄宽度范围内,改变标识值,返回true。表示onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)中的MotionEvent.ACTION_DOWN事件不再处理。
上一个Action down事件处理完后,接下来响应dispatchTouchEvent(MotionEvent ev)的MotionEvent.ACTION_MOVE,代码如下:
case MotionEvent.ACTION_MOVE: Log.i(TAG, "dispatchTouchEvent(): ACTION_MOVE"); int margin = getWidth() - (int) ev.getX(); if (margin < mHandlebarWidth && mAllowScroll) { Log.e(TAG, "dispatchTouchEvent ACTION_MOVE margin = " + margin + "\t mHandlebarWidth = " + mHandlebarWidth); return true; } break;
在MotionEvent.ACTION_MOVE代码块中,发现条件是满足的,返回true。表示和MotionEvent.ACTION_DOWN的一样,在后续的回调方法内Action move将不再处理。最后看当手指抬起,处理的代码如下:
case MotionEvent.ACTION_UP: Log.i(TAG, "dispatchTouchEvent(): ACTION_UP"); if (isClick && mPanelInvisible && mAllowScroll) { isClick = false; mPanelInvisible = false; int scrollX = getChildAt(1).getScrollX(); mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME); invalidate(); return true; } break;
上面的代码片段含义:当用户手指在屏幕上按下,当前ViewGroup中没有子View正在滚动,左侧面板处于可见,手指按下的坐标X值是在手柄(B视图的当前可见部分)的宽度范围内,则down事件到此处理结束。在move代码块里同样判断用户当前手指按下的坐标X值是在手柄宽度范围内的,则move事件也到此结束。在手指抬起后,up事件里判断在down是事件里设置的标识,发现条件都满足,响应用户在手柄上的单击事件请求。也就是说父View把用户的请求已响应了,不用传递到子View。子View也不知道发生过这件事。(大概意思就是这样)
触控区域太大的问题,每次都判断用户手指按下的坐标x值,是否在手柄宽度范围内(不管左侧面板是否可见)。灵敏度的问题,其实就是响应滚动子View的临界值的大小问题,值越小灵敏都越高。
ScrollerContainer类,修改后的代码如下:
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 static final int ANIMATION_DURATION_TIME = 300; /** * 记录当前的滑动结束后的状态,左侧面板是否可见 * true 向右滑动(左侧面板处于可见) * false 向左滑动(左侧面板处于不可见) */ private boolean mPanelInvisible; /** * 是否已滑动结束 */ private boolean mFinished; /** * 是否允许滚动 * 满足的条件: * 左侧面板可见,当前手指按下的坐标x值 ,是在手柄宽度范围内; * 左侧面板不可见,当前手指按下的坐标x值 < 手柄宽度 */ private boolean mAllowScroll; /** * 是否满足响应单击事件的条件 * 满足的条件:左侧面板可见,当前手指按下的坐标x值 ,是在手柄宽度范围内 */ private boolean isClick; public ScrollerContainer(Context context) { super(context); mScroller = new Scroller(context); mHandlebarWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 70, getResources().getDisplayMetrics()); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { Log.e(TAG, "dispatchTouchEvent()"); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG, "dispatchTouchEvent(): ACTION_DOWN"); mFinished = mScroller.isFinished(); if(mFinished){ int x = (int) ev.getX(); int width = getWidth(); if(mPanelInvisible)// 左侧面板可见 { if(x > (width - mHandlebarWidth)){ // 当前手指按下的坐标x值 ,是在手柄宽度范围内 isClick = true; mAllowScroll = true; return true; } else { isClick = false; mAllowScroll = false; } } else { // 左侧面板不可见 if(x < mHandlebarWidth ){ // 当前手指按下的坐标x值 < 手柄宽度 (也就是说在手柄宽度范围内,是可以相应用户的向右滑动手势) mAllowScroll = true; }else{ mAllowScroll = false; } } } else { // 当前正在滚动子View,其它的事不响应 return false; } break; case MotionEvent.ACTION_MOVE: Log.i(TAG, "dispatchTouchEvent(): ACTION_MOVE"); int margin = getWidth() - (int) ev.getX(); if (margin < mHandlebarWidth && mAllowScroll) { Log.e(TAG, "dispatchTouchEvent ACTION_MOVE margin = " + margin + "\t mHandlebarWidth = " + mHandlebarWidth); return true; } break; case MotionEvent.ACTION_UP: Log.i(TAG, "dispatchTouchEvent(): ACTION_UP"); if (isClick && mPanelInvisible && mAllowScroll) { isClick = false; mPanelInvisible = false; int scrollX = getChildAt(1).getScrollX(); mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME); invalidate(); return true; } break; case MotionEvent.ACTION_CANCEL: Log.i(TAG, "dispatchTouchEvent(): ACTION_CANCEL"); break; default: break; } return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { Log.e(TAG, "onInterceptTouchEvent()"); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG, "onInterceptTouchEvent(): ACTION_DOWN"); mFinished = mScroller.isFinished(); if(!mFinished){ return false; } break; case MotionEvent.ACTION_MOVE: Log.i(TAG, "onInterceptTouchEvent(): ACTION_MOVE"); mVelocityTracker = VelocityTracker.obtain(); mVelocityTracker.addMovement(ev); // 一秒时间内移动了多少个像素 mVelocityTracker.computeCurrentVelocity(1000, ViewConfiguration.getMaximumFlingVelocity()); float velocityValue = Math.abs(mVelocityTracker.getXVelocity()) ; Log.d(TAG, "onInterceptTouchEvent(): mVelocityValue = " + velocityValue); if (velocityValue > 300 && mAllowScroll) { return true; } break; case MotionEvent.ACTION_UP: Log.i(TAG, "onInterceptTouchEvent(): ACTION_UP"); if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } break; case MotionEvent.ACTION_CANCEL: Log.i(TAG, "onInterceptTouchEvent(): ACTION_CANCEL"); break; default: break; } return super.onInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { Log.e(TAG, "onTouchEvent()"); float x = event.getX(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: Log.i(TAG, "onTouchEvent(): ACTION_DOWN"); mFinished = mScroller.isFinished(); if(!mFinished){ return false; } 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"); if(!mAllowScroll){ break; } float width = getWidth(); // 响应滚动子View的临界值,若觉得响应过于灵敏,可以将只改大些。 // 比如:criticalWidth = width / 3或criticalWidth = width / 2,看情况而定,呵呵。 float criticalWidth = width / 5; Log.i(TAG, "onTouchEvent(): ACTION_UP x = " + x + "\t criticalWidth = " + criticalWidth); int scrollX = getChildAt(1).getScrollX(); if ( x < criticalWidth) { Log.i(TAG, "onTouchEvent(): ACTION_UP 向左滑动"); mPanelInvisible = false; mScroller.startScroll(scrollX, 0, -scrollX, 0, ANIMATION_DURATION_TIME); invalidate(); } else if ( x > criticalWidth){ Log.i(TAG, "onTouchEvent(): ACTION_UP 向右滑动"); mPanelInvisible = true; 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); } @Override public void computeScroll() { // super.computeScroll(); if(mScroller.computeScrollOffset()){ this.getChildAt(1).scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); this.postInvalidate(); } } /** * 向右滑动View,让左侧操作面饭可见 */ public void slideToRight() { mFinished = mScroller.isFinished(); if(mFinished && !mPanelInvisible){ mPanelInvisible = true; 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(); } }
主应用界面源码:
package com.everyone.android.ui; import android.os.Bundle; import android.view.ViewGroup.LayoutParams; import com.everyone.android.AppBaseActivity; 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; /** * 功能描述:应用主界面 * @author android_ls * */ public class EveryoneActivity extends AppBaseActivity implements OnSlideListener { /** * 滚动(滑动)容器 */ private ScrollerContainer mSlideContainer; /** * 左侧面板 */ private LeftPanelLayout mLeftPanelLayout; /** * 新鲜事 */ private FreshNewsLayout mFreshNewsLayout; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(mSlideContainer); } @Override protected int getLayoutId() { return 0; } @Override protected void setupView() { mSlideContainer = new ScrollerContainer(mContext); mLeftPanelLayout = new LeftPanelLayout(mContext); mFreshNewsLayout = new FreshNewsLayout(mContext); mFreshNewsLayout.setOnSlideListener(this); LayoutParams layoutParams = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); mSlideContainer.addView(mLeftPanelLayout, layoutParams); mSlideContainer.addView(mFreshNewsLayout, layoutParams); } @Override protected void initialized() { // TODO Auto-generated method stub } @Override public void toLeft() { // TODO Auto-generated method stub } @Override public void toRight() { mSlideContainer.slideToRight(); } }
AppBaseActivity类的修改部分,在onCreate里添加的处理:
int layoutId = getLayoutId(); if(layoutId != 0){ setContentView(getLayoutId()); }
有关滑动菜单的到这里就完了,后面在使用过程中遇到什么问题,再处理。
看下效果图,还和上一篇一样,静态的图片看不出来优化后的效果,不过还是上传几张,有图有真相。
向右滑动或点击顶部箭头后
相关文章推荐
- Android仿人人客户端(v5.7.1)——采用RelativeLayout做父容器,实现左侧滑动菜单(一)
- Android仿人人客户端(v5.7.1)——采用RelativeLayout做父容器,实现左侧滑动菜单(一)
- Android仿人人客户端(v5.7.1)——采用RelativeLayout做父容器,实现左侧滑动菜单(一)
- Android仿人人客户端(v5.7.1)——采用ViewGroup做父容器,实现左侧滑动菜单(三)
- [置顶] Android仿人人客户端(v5.7.1)——采用ViewGroup做父容器,实现左侧滑动菜单(三)
- Android仿人人客户端(v5.7.1)——有关滑动式左侧菜单实现过程中网友的疑问解答
- Android仿人人客户端(v5.7.1)——应用主界面之左侧面板UI实现
- [置顶] Android仿人人客户端(v5.7.1)——应用主界面之左侧面板UI实现
- Android仿人人客户端(v5.7.1)——对从服务器端(网络)获取的图片进行本地双缓存处理(编码实现)
- Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑实现
- Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑实现
- Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑实现
- Android仿人人客户端(v5.7.1)——点击左侧菜单栏中的Item切换视图
- Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑实现
- Android仿人人客户端(v5.7.1)——欢迎和导引界面的编码实现
- Android仿人人客户端(v5.7.1)——点击左侧菜单栏中的Item切换视图
- Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑实现
- Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑实现
- 图片、缓存-Android仿人人客户端(v5.7.1)——对从服务器端(网络)获取的图片进行本地双缓存处理(编码实现)-by小雨
- Android滑动菜单特效实现,仿人人客户端侧滑效果,史上最简单的侧滑实现