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

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

 
阅读更多

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

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e(TAG, "ParentContainer : onInterceptTouchEvent()");

        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public 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() {

            @Override
            public void onClick(View v) {
                Log.i(TAG, "ChildContainer : 我响应了单击事件");
            }
        });

        LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        this.addView(btnTest, params);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i(TAG, "ChildContainer : onInterceptTouchEvent()");

        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i(TAG, "ChildContainer : onTouchEvent()");

        return super.onTouchEvent(event);
    }

}

测试Activity代码:

public class TestActivity extends Activity {
    
    @Override
    public 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());
    }

    @Override
    public 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);
    }

    @Override
    public 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);
    }

    @Override
    public 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;

    @Override
    public 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视图移动到回去(恢复默认显示),代码如下:

    @Override
    public 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;

    @Override
    public 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);
    }

    @Override
    public void toLeft() {
        // TODO Auto-generated method stub

    }

    @Override
    public void toRight() {
        mSlideContainer.slideToRight();
    }

}
分享到:
评论

相关推荐

    易语言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

    JitBit Macro Recorder是一款用于记录键盘鼠标动作的小工具,虽然它的体积很小但是功能却不容小看,它能够轻松记录下所有的键盘和鼠标动作,录制之后可以进行编辑以及回放,你也可以制作成EXE格式的文件发送给朋友...

    Rosetta Stone-v5.7.1_build_50701017.apk

    Rosetta Stone-v5.7.1_build_50701017.apk

    TMS_Component_pack_fullsource V5.7.1安装版

    TMS_Component_pack_fullsource安装版 V5.7.1

    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系统开发(移植)和应用...

    int2e-HPSocket.Net-master_v5.7.1.zip

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

    MONyog v5.7.1-0 MySQL Monitor.zip

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

    node-v5.7.1.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 ...

    node-v5.7.1.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 ...

    pypy2-v5.7.1-src.zip

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

    pypy3-v5.7.1-src.zip

    TensorFlow是一个开放源代码的软件库,用于进行高性能数值计算。通过其灵活的架构,它允许用户轻松地部署计算工作在各种平台(CPUs、GPUs、TPUs)上,无论是在桌面、服务器还是移动设备上。TensorFlow最初由Google ...

    pypy2-v5.7.1-win32.zip

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

    Access2MySQL Pro v5.7.1

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

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

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

    万能票据打印专家-PC V 5.7.1授权直装版

    精锐万能票据打印专家是精锐软件面向票据处理市场推出的一款专业票据打印软件,利用该软件可轻松打印现金支票、转帐支票、旅行支票、转账支票、贷记凭证、电汇凭证、信汇凭证、进账单、现金结款单、快递单、汇/本票...

    【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主题、...

Global site tag (gtag.js) - Google Analytics