`
xuela_net
  • 浏览: 495690 次
文章分类
社区版块
存档分类
最新评论

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

 
阅读更多

转载请标明出处: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());
 }

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

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

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

分享到:
评论

相关推荐

    易语言v5.7.1[极致精简版]

    易语言v5.7.1[极致精简版]易语言v5.7.1[极致精简版]易语言v5.7.1[极致精简版]

    MONyog v5.7.1-0 MySQL Monitor Ultimate for Windows

    MONyog MySQL Monitor Ultimate v5.7.1-0,带序列号,本人亲测可用。 此前由于Webyog频繁的调整注册机制,之前的大部分Key都被封杀后,很久一段时间都用不了5.3以后的官网新版。 现在终于有可以使用的新版了,下载...

    HP-Socket v5.7.1.rar

    HP-Socket v5.7.1版本,2019年12月22日最新的资料,支持C,C++,Delphi和E语言,具体C#语言另一个文件上传,这个文件太大了,传不上去了

    Macro Recorder v5.7.1注册版.rar

    软件介绍: 注:本软件需要安装.net4.0框架,否则无法继续安装。...通过这些功能强大的命令,偿能够做也具有特别功能的脚本程序,可以用于制作游戏外G等。压缩包内附序列号,填写上后即是注册版!

    Rosetta Stone-v5.7.1_build_50701017.apk

    Rosetta Stone-v5.7.1_build_50701017.apk

    node-v5.7.1-sunos-x86.tar.gz

    node-v5.7.1-sunos-x86.tar

    TMS_Component_pack_fullsource V5.7.1安装版

    TMS_Component_pack_fullsource安装版 V5.7.1

    node-v5.7.1-linux-ppc64le.tar.gz

    node-v5.7.1-linux-ppc64le.tar

    Android技术内幕.系统卷(扫描版)

    《android技术内幕:系统卷》 前言 第1章 准备工作 /1 1.1 深入认识android /2 1.1.1 android的系统构架 /2 1.1.2 android的初始化流程 /5 1.1.3 各个层次之间的相互关系 /8 1.1.4 android系统开发(移植)和应用...

    MONyog v5.7.1-0 MySQL Monitor.zip

    MySQL监控工具 MONyog可以动态地监控企业数据库环境,并针对MySQL系统安全性、数据库优化以及减少停机时间等提供专家意见。

    int2e-HPSocket.Net-master_v5.7.1.zip

    HPSocket_v5.7.1版本所支持C#文件,需要的可以进行下载,目前国内号称最好的库,超级稳定的一个网络开发库,谢谢

    Access2MySQL Pro v5.7.1

    Access2MySQL 是能允许你转换.mdb((Microsoft Access 数据库)数据库为MySQL以及反之亦然的高效应用程序.不像其他只能进行单向转换的工具.除此之外,Acces2MySQL 支持数据库同步,可以避免重复数据。

    node-v5.7.1.tar.xz

    Node.js,简称Node,是一个...在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    node-v5.7.1.tar.gz

    Node.js,简称Node,是一个...在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    【WordPress插件】2022年最新版完整功能demo+插件v5.7.1.zip

    "【WordPress插件】2022年最新版完整功能demo+插件v5.7.1 Stylish Cost Calculator Premium 时尚成本计算器溢价" ---------- 泰森云每天更新发布最新WordPress主题、HTML主题、WordPress插件、shopify主题、...

    【WordPress插件】2022年最新版完整功能demo+插件v5.7.1 Nulled.zip

    "【WordPress插件】2022年最新版完整功能demo+插件v5.7.1 Nulled ARForms: Wordpress Form Builder Plugin Arforms:WordPress Form Builder插件" ---------- 泰森云每天更新发布最新WordPress主题、HTML主题、...

    pypy2-v5.7.1-src.zip

    Python库是一组预先编写的代码模块,旨在帮助开发者实现特定的编程任务,无需从零开始编写代码。这些库可以包括各种功能,如数学运算、文件操作、数据分析和网络编程等。Python社区提供了大量的第三方库,如NumPy、...

    hue-3.9.0-cdh5.7.1

    hue-3.9.0-cdh5.7.1 源码 hue-3.9.0-cdh5.7.1 源码 hue-3.9.0-cdh5.7.1 源码

Global site tag (gtag.js) - Google Analytics