完美解决ListView中子项焦点不可被Touch的BUG.

1.在Eclipse中新建一个Android项目,项目名就叫做SlideMenuTest.
public class SlidingLayout extends RelativeLayout implements OnTouchListener {
    /**
     * 滚动显示和隐藏左侧布局时,手指滑动需要达到的速度。
     */
    public static final int SNAP_VELOCITY = 200;
    /**
     * 屏幕宽度值。
     */
    private int screenWidth;
    /**
     * 右侧布局最多可以滑动到的左边缘。
     */
    private int leftEdge = 0;
    /**
     * 右侧布局最多可以滑动到的右边缘。
     */
    private int rightEdge = 0;
    /**
     * 在被判定为滚动之前用户手指可以移动的最大值。
     */
    private int touchSlop;
    /**
     * 记录手指按下时的横坐标。
     */
    private float xDown;
    /**
     * 记录手指按下时的纵坐标。
     */
    private float yDown;
    /**
     * 记录手指移动时的横坐标。
     */
    private float xMove;
    /**
     * 记录手指移动时的纵坐标。
     */
    private float yMove;
    /**
     * 记录手机抬起时的横坐标。
     */
    private float xUp;
    /**
     * 左侧布局当前是显示还是隐藏。只有完全显示或隐藏时才会更改此值,滑动过程中此值无效。
     */
    private boolean isLeftLayoutVisible;
    /**
     * 是否正在滑动。
     */
    private boolean isSliding;
    /**
     * 左侧布局对象。
     */
    private View leftLayout;
    /**
     * 右侧布局对象。
     */
    private View rightLayout;
    /**
     * 用于监听侧滑事件的View。
     */
    private View mBindView;
    /**
     * 左侧布局的参数,通过此参数来重新确定左侧布局的宽度,以及更改leftMargin的值。
     */
    private MarginLayoutParams leftLayoutParams;
    /**
     * 右侧布局的参数,通过此参数来重新确定右侧布局的宽度。
     */
    private MarginLayoutParams rightLayoutParams;
    /**
     * 用于计算手指滑动的速度。
     */
    private VelocityTracker mVelocityTracker;
    /**
     * 重写SlidingLayout的构造函数,其中获取了屏幕的宽度。
     * 
     * @param context
     * @param attrs
     */
    public SlidingLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        screenWidth = wm.getDefaultDisplay().getWidth();
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();//获得触发控件的最短距离
    }
    /**
     * 绑定监听侧滑事件的View,即在绑定的View进行滑动才可以显示和隐藏左侧布局。
     * 
     * @param bindView
     *            需要绑定的View对象。
     */
    public void setScrollEvent(View bindView) {
        mBindView = bindView;
        mBindView.setOnTouchListener(this);  //绑定监听事件
    }
    /**
     * 将屏幕滚动到左侧布局界面,滚动速度设定为30.
     */
    public void scrollToLeftLayout() {     
        if (leftLayout.getVisibility() != View.VISIBLE) {  //leftLayout如果不是可见
            leftLayout.setVisibility(View.VISIBLE);//设置可见状态
        }
        new ScrollTask().execute(-30);
    }
    /**
     * 将屏幕滚动到右侧布局界面,滚动速度设定为-30.
     */
    public void scrollToRightLayout() {
        new ScrollTask().execute(30);
    }
    /**
     * 左侧布局是否完全显示出来,或完全隐藏,滑动过程中此值无效。
     * 
     * @return 左侧布局完全显示返回true,完全隐藏返回false。
     */
    public boolean isLeftLayoutVisible() {
        return isLeftLayoutVisible;
    }
    /**
     * 在onLayout中重新设定左侧布局和右侧布局的参数。
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (changed) {
            // 获取左侧布局对象
            leftLayout = getChildAt(0);
            leftLayoutParams = (MarginLayoutParams) leftLayout.getLayoutParams();
            rightEdge = -leftLayoutParams.width;
            // 获取右侧布局对象
            rightLayout = getChildAt(1);
            rightLayoutParams = (MarginLayoutParams) rightLayout.getLayoutParams();
            rightLayoutParams.width = screenWidth;
            rightLayout.setLayoutParams(rightLayoutParams);
        }
    }
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        createVelocityTracker(event);
        if (leftLayout.getVisibility() != View.VISIBLE) {
            leftLayout.setVisibility(View.VISIBLE);
        }
        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 手指按下时,记录按下时的横坐标
            xDown = event.getRawX();
            yDown = event.getRawY();
            break;
        case MotionEvent.ACTION_MOVE:
            // 手指移动时,对比按下时的横坐标,计算出移动的距离,来调整右侧布局的leftMargin值,从而显示和隐藏左侧布局
            xMove = event.getRawX();
            yMove = event.getRawY();
            int moveDistanceX = (int) (xMove - xDown);
            int distanceY = (int) (yMove - yDown);
            if (!isLeftLayoutVisible && moveDistanceX >= touchSlop
                    && (isSliding || Math.abs(distanceY) <= touchSlop)) {
                isSliding = true;
                rightLayoutParams.rightMargin = -moveDistanceX;
                if (rightLayoutParams.rightMargin > leftEdge) {
                    rightLayoutParams.rightMargin = leftEdge;
                }
                rightLayout.setLayoutParams(rightLayoutParams);
            }
            if (isLeftLayoutVisible && -moveDistanceX >= touchSlop) {
                isSliding = true;
                rightLayoutParams.rightMargin = rightEdge - moveDistanceX;
                if (rightLayoutParams.rightMargin < rightEdge) {
                    rightLayoutParams.rightMargin = rightEdge;
                }
                rightLayout.setLayoutParams(rightLayoutParams);
            }
            break;
        case MotionEvent.ACTION_UP:
            xUp = event.getRawX();
            int upDistanceX = (int) (xUp - xDown);
            if (isSliding) {
                // 手指抬起时,进行判断当前手势的意图,从而决定是滚动到左侧布局,还是滚动到右侧布局
                if (wantToShowLeftLayout()) {
                    if (shouldScrollToLeftLayout()) {
                        scrollToLeftLayout();
                    } else {
                        scrollToRightLayout();
                    }
                } else if (wantToShowRightLayout()) {
                    if (shouldScrollToRightLayout()) {
                        scrollToRightLayout();
                    } else {
                        scrollToLeftLayout();
                    }
                }
            } else if (upDistanceX < touchSlop && isLeftLayoutVisible){
                scrollToRightLayout();
            }
            recycleVelocityTracker();
            break;
        }
        if (v.isEnabled()) {
            if (isSliding) {
                unFocusBindView();
                return true;
            }
            if (isLeftLayoutVisible) {
                return true;
            }
            return false;
        }
        return true;
    }
    /**
     * 判断当前手势的意图是不是想显示右侧布局。如果手指移动的距离是负数,且当前左侧布局是可见的,则认为当前手势是想要显示右侧布局。
     * 
     * @return 当前手势想显示右侧布局返回true,否则返回false。
     */
    private boolean wantToShowRightLayout() {
        return xUp - xDown < 0 && isLeftLayoutVisible;
    }
    /**
     * 判断当前手势的意图是不是想显示左侧布局。如果手指移动的距离是正数,且当前左侧布局是不可见的,则认为当前手势是想要显示左侧布局。
     * 
     * @return 当前手势想显示左侧布局返回true,否则返回false。
     */
    private boolean wantToShowLeftLayout() {
        return xUp - xDown > 0 && !isLeftLayoutVisible;
    }
    /**
     * 判断是否应该滚动将左侧布局展示出来。如果手指移动距离大于屏幕的1/2,或者手指移动速度大于SNAP_VELOCITY,
     * 就认为应该滚动将左侧布局展示出来。
     * 
     * @return 如果应该滚动将左侧布局展示出来返回true,否则返回false。
     */
    private boolean shouldScrollToLeftLayout() {
        return xUp - xDown > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
    }
    /**
     * 判断是否应该滚动将右侧布局展示出来。如果手指移动距离加上leftLayoutPadding大于屏幕的1/2,
     * 或者手指移动速度大于SNAP_VELOCITY, 就认为应该滚动将右侧布局展示出来。
     * 
     * @return 如果应该滚动将右侧布局展示出来返回true,否则返回false。
     */
    private boolean shouldScrollToRightLayout() {
        return xDown - xUp > leftLayoutParams.width / 2 || getScrollVelocity() > SNAP_VELOCITY;
    }
    /**
     * 创建VelocityTracker对象,并将触摸事件加入到VelocityTracker当中。
     * 
     * @param event
     *            右侧布局监听控件的滑动事件
     */
    private void createVelocityTracker(MotionEvent event) {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
    }
    /**
     * 获取手指在右侧布局的监听View上的滑动速度。
     * 
     * @return 滑动速度,以每秒钟移动了多少像素值为单位。
     */
    private int getScrollVelocity() {
        mVelocityTracker.computeCurrentVelocity(1000);
        int velocity = (int) mVelocityTracker.getXVelocity();
        return Math.abs(velocity);
    }
    /**
     * 回收VelocityTracker对象。
     */
    private void recycleVelocityTracker() {
        mVelocityTracker.recycle();
        mVelocityTracker = null;
    }
    /**
     * 使用可以获得焦点的控件在滑动的时候失去焦点。
     */
    private void unFocusBindView() {
        if (mBindView != null) {
            mBindView.setPressed(false);
            mBindView.setFocusable(false);
            mBindView.setFocusableInTouchMode(false);
        }
    }
    class ScrollTask extends AsyncTask<Integer, Integer, Integer> {
        @Override
        protected Integer doInBackground(Integer... speed) {
            int rightMargin = rightLayoutParams.rightMargin;
            // 根据传入的速度来滚动界面,当滚动到达左边界或右边界时,跳出循环。
            while (true) {
                rightMargin = rightMargin + speed[0];
                if (rightMargin < rightEdge) {
                    rightMargin = rightEdge;
                    break;
                }
                if (rightMargin > leftEdge) {
                    rightMargin = leftEdge;
                    break;
                }
                publishProgress(rightMargin);
                // 为了要有滚动效果产生,每次循环使线程睡眠20毫秒,这样肉眼才能够看到滚动动画。
                sleep(15);
            }
            if (speed[0] > 0) {
                isLeftLayoutVisible = false;
            } else {
                isLeftLayoutVisible = true;
            }
            isSliding = false;
            return rightMargin;
        }
        @Override
        protected void onProgressUpdate(Integer... rightMargin) {
            rightLayoutParams.rightMargin = rightMargin[0];
            rightLayout.setLayoutParams(rightLayoutParams);
            unFocusBindView();
        }
        @Override
        protected void onPostExecute(Integer rightMargin) {
            rightLayoutParams.rightMargin = rightMargin;
            rightLayout.setLayoutParams(rightLayoutParams);
        }
    }
    /**
     * 使当前线程睡眠指定的毫秒数。
     * 
     * @param millis
     *            指定当前线程睡眠多久,以毫秒为单位
     */
    private void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
2.activity_main.xml
<com.example.slidinglayout.SlidingLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/slidingLayout"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >
    <RelativeLayout
        android:id="@+id/menu"
        android:layout_width="270dip"
        android:layout_height="fill_parent"
        android:layout_alignParentLeft="true"
        android:background="#00ccff"
        android:visibility="invisible" >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="This is menu"
            android:textColor="#000000"
            android:textSize="28sp" />
    </RelativeLayout>
    <LinearLayout
        android:id="@+id/content"
        android:layout_width="320dip"
        android:layout_height="fill_parent"
        android:layout_alignParentRight="true"
        android:background="#e9e9e9"
        android:orientation="vertical"
        android:visibility="visible" >
        <Button
            android:id="@+id/menuButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Menu" />
        <ListView
            android:id="@+id/contentList"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:cacheColorHint="#00000000" >
        </ListView>
    </LinearLayout>

</com.example.slidinglayout.SlidingLayout>

3.MainActivity.java
public class MainActivity extends Activity {
    /**
     * 侧滑布局对象,用于通过手指滑动将左侧的菜单布局进行显示或隐藏。
     */
    private SlidingLayout slidingLayout;
    /**
     * menu按钮,点击按钮展示左侧布局,再点击一次隐藏左侧布局。
     */
    private Button menuButton;
    /**
     * 放在content布局中的ListView。
     */
    private ListView contentListView;
    /**
     * 作用于contentListView的适配器。
     */
    private ArrayAdapter<String> contentListAdapter;
    /**
     * 用于填充contentListAdapter的数据源。
     */
    private String[] contentItems = { "Content Item 1", "Content Item 2", "Content Item 3",
            "Content Item 4", "Content Item 5", "Content Item 6", "Content Item 7",
            "Content Item 8", "Content Item 9", "Content Item 10", "Content Item 11",
            "Content Item 12", "Content Item 13", "Content Item 14", "Content Item 15",
            "Content Item 16" };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        slidingLayout = (SlidingLayout) findViewById(R.id.slidingLayout);
        menuButton = (Button) findViewById(R.id.menuButton);
        contentListView = (ListView) findViewById(R.id.contentList);
        contentListAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
                contentItems);
        contentListView.setAdapter(contentListAdapter);
        // 将监听滑动事件绑定在contentListView上
        slidingLayout.setScrollEvent(contentListView);
        menuButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (slidingLayout.isLeftLayoutVisible()) {
                    slidingLayout.scrollToRightLayout();
                } else {
                    slidingLayout.scrollToLeftLayout();
                }
            }
        });
        contentListView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                String text = contentItems[position];
                Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
            }
        });
    }







android自定义SlideMenu的更多相关文章

  1. android自定义SlideMenu源码详解之最简单侧滑实现

    实现原理:在一个Activity的布局中需要有两部分,一个是菜单(menu)的布局,一个是内容(content)的布局.两个布局横向排列,菜单布局在左,内容布局在右.初始化的时候将菜单布局向左偏移,以 ...

  2. android 自定义动画

    android自定义动画注意是继承Animation,重写里面的initialize和applyTransformation,在initialize方法做一些初始化的工作,在applyTransfor ...

  3. Android自定义View 画弧形,文字,并增加动画效果

    一个简单的Android自定义View的demo,画弧形,文字,开启一个多线程更新ui界面,在子线程更新ui是不允许的,但是View提供了方法,让我们来了解下吧. 1.封装一个抽象的View类   B ...

  4. Android自定义View4——统计图View

    1.介绍 周末在逛慕课网的时候,看到了一张学习计划报告图,详细记录了自己一周的学习情况,天天都是0节课啊!正好在学习Android自定义View,于是就想着自己去写了一个,这里先给出一张慕课网的图,和 ...

  5. (转)[原] Android 自定义View 密码框 例子

    遵从准则 暴露您view中所有影响可见外观的属性或者行为. 通过XML添加和设置样式 通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 ...

  6. Android 自定义View合集

    自定义控件学习 https://github.com/GcsSloop/AndroidNote/tree/master/CustomView 小良自定义控件合集 https://github.com/ ...

  7. Android 自定义View (五)——实践

    前言: 前面已经介绍了<Android 自定义 view(四)-- onMeasure 方法理解>,那么这次我们就来小实践下吧 任务: 公司现有两个任务需要我完成 (1)监测液化天然气液压 ...

  8. Android 自定义 view(四)—— onMeasure 方法理解

    前言: 前面我们已经学过<Android 自定义 view(三)-- onDraw 方法理解>,那么接下我们还需要继续去理解自定义view里面的onMeasure 方法 推荐文章: htt ...

  9. Android 自定义 view(三)—— onDraw 方法理解

    前言: 上一篇已经介绍了用自己定义的属性怎么简单定义一个view<Android 自定义view(二) -- attr 使用>,那么接下来我们继续深究自定义view,下一步将要去简单理解自 ...

随机推荐

  1. POJ2154 Color(Polya定理)

    Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 11654   Accepted: 3756 Description Bead ...

  2. js点击获取标签里面id属性

    <html xmlns="http://www.w3.org/1999/xhtml"> <head > <title></title> ...

  3. ubuntu16.06+vsftpd+nginx搭建图片服务器

    安装vsftpd 注:以下指令都在root账户下操作 # apt安装vsftpd apt-get install vsftpd #启动vsftpd service vsftpd start #新建用户 ...

  4. 【Python 2 到 3 系列】 print 是函数

    v3.0 以前,print一直作为语法结构存在,他是python语法的一部分:这个理解起来可能有点蹩脚,但的确是这样. print 一直被定以为一个statement,也就是说,他跟return/tr ...

  5. 微信小程序插件内页面跳转和参数传递

    在此以插件开发中文章列表跳传文章详情为例. 1.首先在插件中的文章列表页面wxml中绑定跳转事件. bindtap='url' data-id="{{item.article_id}}&qu ...

  6. C#简单的文件阅读器

    写一个简单的文件阅读器  1.可以读取大文件(2G)2.实现首页.下一页.前一页.末页的跳转3.实现到指定页面的跳转,比如跳转到第**页4.限制每页显示字符数 1029-4069byte,且用户可自定 ...

  7. [Bzoj3252]攻略(dfs序+线段树)

    Description 题目链接 Solution 可以想到,每次肯定是拿最大价值为最优 考虑改变树上一个点的值,只会影响它的子树,也就是dfs序上的一个区间, 于是可以以dfs序建线段树,这样就变成 ...

  8. [KMP][BZOJ1355][Baltic2009]Radio Transmission

    题面 Description 给你一个字符串,它是由某个字符串不断自我连接形成的. 但是这个字符串是不确定的,现在只想知道它的最短长度是多少. Input 第一行给出字符串的长度,\(1 < L ...

  9. perl语言入门总结-第4章-子程序

    子程序定义和返回值 sub sum{ print "调用了子程序\n"; $a + $b; #后一行为返回值 } ; ; $s =∑ #34 调用子程序 子程序中的参数,参数固定( ...

  10. Installation error: INSTALL_FAILED_CANCELLED_BY_USER

    我的手机本来是支持Androidstadio 调试手机的,我手机小米的,后来,系统升级了,我也没在意,第二天上班,已运行就报错: Installation error: INSTALL_FAILED_ ...