ListView Item侧滑菜单

一 概述

当下从QQ主界面出现Item侧滑菜单出现之后,很多公司的项目都需要添加这种让人感觉炫酷的功能,但是,目前android API中根本没有提供这种实现,因此就要用自定义的方法来实现该功能。本篇文章实现的基本思路如下:
1.自定义ListView,获取当前滑动的View。
2.自定义LinearLayout充当Item根布局。
3.ListView中按下后,事件交由自定义LinearLayout处理。

二 效果

三 具体实现

  1. 重写ListView 的onTouchEvent方法,代码如下:
@Override
    public boolean onTouchEvent(MotionEvent ev)
    {
        switch (ev.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                int x = (int) ev.getX();
                int y = (int) ev.getY();
                int position = pointToPosition(x,y);
                if (position != INVALID_POSITION)
                {
                    mSwipeMenuLayout = (SwipeMenuLayout) getChildAt(position - getFirstVisiblePosition());
                }
                break;
        }
        if (mSwipeMenuLayout != null)
        {
            mSwipeMenuLayout.onTouchEvent(ev);
        }
        return super.onTouchEvent(ev);
    }
这些代码相对比较简单,没有什么复杂的逻辑,比较关键的是获取用户按下的点,  
用ListView的pointToPosition方法,传入获取的x,y坐标得到当下点击  
的Item的position,重点说一下 ListView的getChildAt(int index)方法,  
因为ListView与GridView的View复用机制,这个方法默认得到的是当前可见  
区域(列表可滚动)的子项!也就表示它的取值范围是在   
>= ListView.getFirstVisiblePosition() &&    
<= ListView.getLastVisiblePosition(); 因此在程序中获取当前  
点击Item View所复用的是哪个Item的View  就需要用   
position(当前通过xy坐标获取到的) -   
(减去) ListView.getFirstVisiblePosition()   
得到的即是复用的Item的position,感兴趣的话,自己可以尝试着看看。

2. 自定义LinearLayout重写其onTouchEvent方法:

/**
 * Created by admin on 2016/1/20.
 */
public class SwipeMenuLayout extends LinearLayout
{
    /**
     * 关闭
     */
    public static final int SLIDE_STATUS_OFF = 0;
    /**
     * 开始滚动
     */
    public static final int SLIDE_STATUS_START_SCROLL = 1;
    /**
     * 已经打开
     */
    public static final int SLIDE_STATUS_ON = 2;
    private static final int TAN = 2;
    /**
     * 滚动类
     */
    private Scroller mScroller;
    /**
     * 屏幕尺寸类
     */
    private int[] screenSize;
    /**
     * 主内容区域
     */
    private ViewGroup mContent;
    /**
     * 菜单区域
     */
    private ViewGroup mMenu;
    /**
     * 默认的宽度
     */
    private int mMenuWidth = 120;

    private int lastX,lastY;

    public SwipeMenuLayout(Context context)
    {
        this(context,null);
    }

    public SwipeMenuLayout(Context context, AttributeSet attrs)
    {
        this(context, attrs,0);
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public SwipeMenuLayout(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    private void init(Context context)
    {
        screenSize = ScreenUtil.getScreenSize(context);
        mMenuWidth = ConvertUtils.dp2px(context,mMenuWidth);
        this.setOrientation(LinearLayout.HORIZONTAL);
        mScroller = new Scroller(context);
    }

    boolean isOnce = false;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        if (!isOnce)
        {
            mContent = (ViewGroup) getChildAt(0);
            mContent.getLayoutParams().width = screenSize[0];
            mMenu = (ViewGroup) getChildAt(1);
            mMenu.getLayoutParams().width = mMenuWidth;
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    /**
     * 设置默认的显示
     */
    public void shrink()
    {
        Log.e("----------",getScrollX() + "");
        if (getScrollX() != 0)
        {
            smoothScrollTo(0, 0);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        /**
         * 重置上次滑动的View
         */
        if (onSlidingListener != null) {
            onSlidingListener.onSliding(this, SLIDE_STATUS_START_SCROLL);
        }
        int x = (int) event.getX();
        int y = (int) event.getY();
        int scrollX = getScrollX();
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:
            {
                /**
                 * 如果没有结束则停止动画
                 */
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }
                break;
            }
            case MotionEvent.ACTION_MOVE:
            {
                int deltaX = x - lastX;
                int deltaY = y - lastY;
                if (Math.abs(deltaX) < Math.abs(deltaY) * TAN) {
                    break;
                }
                int newScrollX = scrollX - deltaX;
                if (deltaX != 0) {
                    if (newScrollX < 0) {
                        newScrollX = 0;
                    } else if (newScrollX > mMenuWidth) {
                        newScrollX = mMenuWidth;
                    }
                    this.scrollTo(newScrollX, 0);
                }
                break;
            }
            case MotionEvent.ACTION_UP:
            {
                int mX = 0;
                if (scrollX - mMenuWidth * 0.75f > 0) {
                    mX = mMenuWidth;
                }
                this.smoothScrollTo(mX, 0);
                if (onSlidingListener != null) {
                    onSlidingListener.onSliding(this, mX == 0 ? SLIDE_STATUS_OFF : SLIDE_STATUS_ON);
                }
                break;
            }
        }
        lastX = x;
        lastY = y;
        return super.onTouchEvent(event);
    }

    /**
     * 换换滚动到的位置
     * @param x
     * @param y
     */
    private void smoothScrollTo(int x, int y)
    {
        int scrollX = getScrollX();
        int dealtX = x - scrollX;
        /**
         * 1.startX
         * 2.startY
         * 3.endX
         * 4.endY
         * 5.duration
         */
        mScroller.startScroll(scrollX,0,dealtX,0,Math.abs(dealtX) * 3);
        invalidate();
    }
    @Override
    public void computeScroll()
    {
        if (mScroller.computeScrollOffset())
        {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }

    public void setOnSlidingListener(OnSlidingListener onSlidingListener)
    {
        this.onSlidingListener = onSlidingListener;
    }

    private OnSlidingListener onSlidingListener;

    public interface OnSlidingListener
    {
        void onSliding(View view, int state);
    }
}

上面SwipeMenuLayout 这个类中
(1) init(context)方法中初始化获取了屏幕的宽高值,滑动出的菜单的宽度设置,在这里只是为了展示效果我设置了一个固定的值,若是你有兴趣,可以尝试调用菜单区域的OnMeasure方法获取设置的值。
(2) OnMeasure设置默认显示方式。
(3) onTouchEvent方法中:首先 发送了一个回调事件,目的是还原上次打开的Item
ACTION_DOWN:若是上次的还未滚动完则终止上次的
ACTION_MOVE: 添加了Item范围的控制,其中scrollX (当前view的左上角相对于母视图的左上角的X轴偏移量) ,deltaX 将要滚动的值 (本次滑动的最后位置,减去上次的位置余下的需要滑动的值),x(本次滑动停留的位置),lastX(保留的上次滑动的最后位置)
ACTION_UP:scrollX (当前view的左上角相对于母视图的左上角的X轴偏移量) 如果这个偏移量的值大于了Menu视图的0.75也就是3/4那么就显示菜单(这个值可以自己设置),发送回调通知菜单展示。
(4) smoothScrollTo(int x,int y)在这个方法中主要说一下 startScroll(startX,startY,endX,endY,duration) startX:起始X轴滚动位置 startY:起始Y轴滚动位置
endX:结束X轴的位置 endY:起始Y轴的位置 duration:间隔时间
那么该类中还有一个方法没有讲:

@Override
    public void computeScroll()
    {
        if (mScroller.computeScrollOffset())
        {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }

这个方法是干什么的呢?当我们执行ontouch或invalidate()或postInvalidate()都会导致这个方法的执行 ,因此我们在这个方法中判断了mScroller的startScroll()方法是否执行完毕,若是完成的话,则滚动到最终位置,并重新绘制界面。

3.事件传递
这个就更简单啦,因为我们重写了ItemView的OnTouchEvent方法,因此在ListView中我们做了引用SwipeMenuLayout来对应当前滑动的Item的View 并调用该Item View的onTouchEvent()传入当前的点击事件,就完成了事件的传递。

4.后记
因为这个代码是第一个完成的初品版本,因此可能在代码中还有没有测到的bug,后面会有源码奉上,若是你在使用的过程中,发现有问题,或者有更好的思路,欢迎你在留言区留言,谢谢。

至此 这个简单的Item侧滑就实现了。代码中没有太多比较难理解的地方,只要我们能稍稍多想一点,了解View的绘制已经生命周期原理。建议大家没事多练练 多看些思路比较清晰的大神的blog 相信你会有所收获:

大神爱哥博客传送
http://my.csdn.net/aigestudio
尤其喜欢他的自定义控件系列

大神郭霖博客传送
http://my.csdn.net/sinyu890807

大神鸿洋博客传送
http://my.csdn.net/lmj623565791







源码下载

你可能感兴趣的:(Android)