效果图:

看网上的都是两个view拼接,默认右侧的不显示,水平移动的时候把右侧的view显示出来。但是看最新版QQ上的效果不是这样的,但给人的感觉却很好,所以献丑来一发比较高仿的。

知识点:

1、ViewDragHelper 的用法;

2、滑动冲突的解决;

3、自定义viewgroup。

ViewDragHelper 出来已经比较久了 相信大家都比较熟悉,不熟悉的话google一大把

这里主要简单用一下它的几个方法

1、tryCaptureView(View child, int pointerId) :确定那个子view可以滑动

2、 getViewHorizontalDragRange(View child):用我蹩脚的英语翻译一下是‘返回的是子view在水平方向上可移动的大小,以像素为单位,返回0的时候表示水平方向上不能拖动’

3、clampViewPositionHorizontal(View child, int left, int dx):在这里可以对边界进行检查,left和dx分别代表即将移动到的位置

4、onViewPositionChanged(View changedView, int left, int top,

int dx, int dy):当要捕获view,由于拖曳或者设定而发生位置变更时回调

它的基本用法是:

    public SwipeLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }
    private void init() {
        viewDragHelper = ViewDragHelper.create(this, callback);
    }
     public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean result = viewDragHelper.shouldInterceptTouchEvent(ev);
     }

      public boolean onTouchEvent(MotionEvent event) {
        viewDragHelper.processTouchEvent(event);
        return true;
    }

1)、在构造方法中创建

2)、在onInterceptTouchEvent 中判断是否拦截

3 )、 在 onTouchEvent出来事件

好了 最不好理解的已经搞定了。接下来看看具体实现:

首先看布局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <scrollviewgroup.lly.com.swiplayout.SwipeLayout
        android:id="@+id/swipeLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <!-- delete区域的布局 -->
        <include layout="@layout/layout_delete" />
        <!-- item内容的布局 -->
        <include layout="@layout/layout_content" />
    </scrollviewgroup.lly.com.swiplayout.SwipeLayout>

</LinearLayout>

这个没什么好说的,一个自定义viewgroup包含两个子控件。

接着看看SwipeLayout是怎么实现的:

 @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        deleteView = getChildAt(0);
        contentView = getChildAt(1);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        deleteHeight = deleteView.getMeasuredHeight();
        deleteWidth = deleteView.getMeasuredWidth();
        contentWidth = contentView.getMeasuredWidth();
        screenWidth = getWidth();
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
                            int bottom) {
        // super.onLayout(changed, left, top, right, bottom);
        deleteView.layout(screenWidth - deleteWidth, 0, (screenWidth - deleteWidth)
                + deleteWidth, deleteHeight);
        contentView.layout(0, 0, contentWidth, deleteHeight);
    }

上面代码进行了一些初始化的操作,重点看看onlayout里面的,我们继承的是framelayout 这里先画出来 deleteView并让他在右边,然后在上面改了一层contentView,这样显示的时候只会显示contentView。

接下来看ontouch方法

public boolean onTouchEvent(MotionEvent event) {
        //如果当前有打开的,则下面的逻辑不能执行
        if(!SwipeLayoutManager.getInstance().isShouldSwipe(this)){
            requestDisallowInterceptTouchEvent(true);
            return true;
        }

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                //1.获取x和y方向移动的距离
                float moveX = event.getX();
                float moveY = event.getY();
                float delatX = moveX - downX;//x方向移动的距离
                float delatY = moveY - downY;//y方向移动的距离
                if(Math.abs(delatX)>Math.abs(delatY)){
                    //表示移动是偏向于水平方向,那么应该SwipeLayout应该处理,请求父view不要拦截
                    requestDisallowInterceptTouchEvent(true);
                }
                //更新downX,downY
                downX = moveX;
                downY = moveY;
                break;
            case MotionEvent.ACTION_UP:

                break;
        }
        viewDragHelper.processTouchEvent(event);
        return true;
    }

上面主要就是对事件冲突的处理,当是水平移动的时候就请求父视图不要拦截。

接下来来重点就来了

 private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return child==contentView;
        }
        @Override
        public int getViewHorizontalDragRange(View child) {
            return deleteWidth;
        }
        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if(child==contentView){
                if(left>0)left = 0;
                if(left<-deleteWidth)left = -deleteWidth;
            }
            return left;
        }
        @Override
        public void onViewPositionChanged(View changedView, int left, int top,
                                          int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);
            //判断开和关闭的逻辑
            if(contentView.getLeft()==0 && currentState!=SwipeState.Close){
                //说明应该将state更改为关闭
                currentState = SwipeState.Close;

                //回调接口关闭的方法
                if(listener!=null){
                    listener.onClose(getTag());
                }

                //说明当前的SwipeLayout已经关闭,需要让Manager清空一下
                SwipeLayoutManager.getInstance().clearCurrentLayout();
            }else if (contentView.getLeft()==-deleteWidth && currentState!=SwipeState.Open) {
                //说明应该将state更改为开
                currentState = SwipeState.Open;

                //回调接口打开的方法
                if(listener!=null){
                    listener.onOpen(getTag());
                }
                //当前的Swipelayout已经打开,需要让Manager记录一下下
                SwipeLayoutManager.getInstance().setSwipeLayout(SwipeLayout.this);
            }
        }
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            if(contentView.getLeft()<-deleteWidth/2){
                //应该打开
                open();
            }else {
                //应该关闭
                close();
            }
        }
    };

上面这段代码里面的方法一开始我们都说过了,在来看下

在tryCaptureView中我们让 contentView可以滑动,在getViewHorizontalDragRange中确定滑动范围是deleteWidth,在clampViewPositionHorizontal中对边界进行了下限制,在onViewPositionChanged中进行状态的更新,

最后在手指抬起的时候让view自动回滚,

 /**
     * 打开的方法
     */
    public void open() {
        viewDragHelper.smoothSlideViewTo(contentView,-deleteWidth,contentView.getTop());
        ViewCompat.postInvalidateOnAnimation(SwipeLayout.this);
    }
    /**
     * 关闭的方法
     */
    public void close() {
        viewDragHelper.smoothSlideViewTo(contentView,0,contentView.getTop());
        ViewCompat.postInvalidateOnAnimation(SwipeLayout.this);
    };
    public void computeScroll() {
        if(viewDragHelper.continueSettling(true)){
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

这里注意一定要重写computeScroll方法,不然滑动效果动一下就不动了。

至此这个自定义framelayout就完成了

但是发现一个问题,我们在已经滑动出来的view中上下滑动时,这个view的deleteView还是显示状态,所以还要在activity中处理一下:

  recyView.setOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if(dy>0 || dy<0){
                    SwipeLayoutManager.getInstance().closeCurrentLayout();
                }
            }
        });

当这个RecyclerView是上下滑动时,让子view复位。

收工。

ps:本来是在eclipse中listview中实现的,但是想想google都已经不支持eclipse了,而listview也快被RecyclerView代替了,所以最后还是切换到android studio,用RecyclerView实现了一套。

源码地址

android 自定义view之侧滑效果的更多相关文章

  1. android自定义view实现公章效果

    上次去一个公司面试,面试官问了一个题,怎么用android的自定义view实现一个公章的效果,据说这是华为之前的面试题,我想了下,要是公章的效果,最外层是一个圆,里面是一个五角星,但是这文字怎么画呢, ...

  2. Android 自定义View跑马灯效果(一)

    今天通过书籍重新复习了一遍自定义VIew,为了加强自己的学习,我把它写在博客里面,有兴趣的可以看一下,相互学习共同进步: 通过自定义一个跑马灯效果,来诠释一下简单的效果: 一.创建一个类继承View, ...

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

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

  4. Android自定义View(LimitScrollerView-仿天猫广告栏上下滚动效果)

    转载请标明出处: http://blog.csdn.net/xmxkf/article/details/53303872 本文出自:[openXu的博客] 1分析 2定义组合控件布局 3继承最外层控件 ...

  5. Android 自定义View合集

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

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

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

  7. Android 自定义view(二) —— attr 使用

    前言: attr 在前一篇文章<Android 自定义view -- attr理解>已经简单的进行了介绍和创建,那么这篇文章就来一步步说说attr的简单使用吧 自定义view简单实现步骤 ...

  8. Android 自定义View及其在布局文件中的使用示例(二)

    转载请注明出处 http://www.cnblogs.com/crashmaker/p/3530213.html From crash_coder linguowu linguowu0622@gami ...

  9. Android自定义View

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24252901 很多的Android入门程序猿来说对于Android自定义View ...

随机推荐

  1. 学习React系列(六)——更新dom细节于原理

    React更新dom的依据: 1.不同类型的elements会产生不同的树 2.通过render方法中包含key属性的子元素,开发者可以示意哪些子元素可能是稳定的. 更新过程: 一.根元素类型不同:旧 ...

  2. Linux OpenGL 实践篇-4 坐标系统

    OpenGL中顶点经过顶点着色器后会变为标准设备坐标系.标准设备坐标系的各坐标的取值范围是[-1,1],超过这个范围的点将会被剔除.而这个变换的过程可描述为顶点在几个坐标系统的变换,这几个坐标系统为: ...

  3. 一文了解安卓APP逆向分析与保护机制

    "知物由学"是网易云易盾打造的一个品牌栏目,词语出自汉·王充<论衡·实知>.人,能力有高下之分,学习才知道事物的道理,而后才有智慧,不去求问就不会知道."知物 ...

  4. 如何降低移动APP的开发成本

    在当下竞争激烈的商业世界中,移动APP开发是您业务的有利补充.移动APP可通过吸引新客户和保留现有客户,帮助公司成功开展业务.定制一个属于自己公司的移动APP扮演着重要角色,手机APP可以说通过轻松处 ...

  5. CSS 权威指南 CSS实战手册 第四版(阅读笔记)

    前言: 对于程序员,学习是无止境的,知识淘换非常快,能够快速稳固掌握一门新技术,是一个程序员应该具备的素质.这里将分析本人一点点不成熟的心得. 了解一门语言,了解它的概念非常重要,但是一些优秀的设计思 ...

  6. [LeetCode] Freedom Trail 自由之路

    In the video game Fallout 4, the quest "Road to Freedom" requires players to reach a metal ...

  7. Python的hasattr() getattr() setattr() 函数使用方法详解 (转)

    来自:https://www.cnblogs.com/cenyu/p/5713686.html hasattr(object, name)判断一个对象里面是否有name属性或者name方法,返回BOO ...

  8. [测试题]神在夏至祭降下了神谕(oracle)

    Description 我们村子在过去的 400 年中, 断绝与下界的接触, 过着自给自足的生活.夏至祭是一场迎接祖灵于夏季归来, 同时祈求丰收的庆典.村里的男人会在广场上演出夏之军和冬之军的战争. ...

  9. [Codeforces]848C - Goodbye Souvenir

    题目大意:n个数字,m次操作,支持修改一个数字和查询一个区间内每种数字最大出现位置减最小出现位置的和.(n,m<=100,000) 做法:把每个数字表示成二维平面上的点,第一维是在数组中的位置, ...

  10. NOIP2014-10-30模拟赛

    T1:逗比三角形 [题目描述] 小J是一名OI退役滚粗文化课选手,他十分喜欢做题,尤其是裸题.他现在有一个二维盒子和一些二维三角形,这个盒子拥有无限的高度和L的宽度.而且他的三角形也都是一些锐角三角形 ...