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

来源:互联网 发布:亳州安广网络客服电话 编辑:程序博客网 时间:2024/05/17 22:39

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

        上一篇在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());        }

有关滑动菜单的到这里就完了,后面在使用过程中遇到什么问题,再处理。

看下效果图,还和上一篇一样,静态的图片看不出来优化后的效果,不过还是上传几张,有图有真相。

向右滑动或点击顶部箭头后

 

 

 

原创粉丝点击