(六)仿QQ首页drawer/侧滑删除/浮动imgaeView/角标拖拽

效果图如下:


demo6.gif

1.首页左侧drawerLaout
借鉴 https://github.com/qiantao94/CoordinatorMenu
小作修改:
因为该库不支持自定义侧边栏的宽度,我这边增加了一个属性drawerPercent,是指侧边栏占据手机屏幕的百分比。

 <com.dl.common.widget.drawerlayout.DrawerMenu
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/menu"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:drawerPercent="0.8">

        <include layout="@layout/demo6_layout_drawer_view" />

        <include layout="@layout/demo6_layout_main_view" />


    </com.dl.common.widget.drawerlayout.DrawerMenu>

2.item侧滑删除
借鉴 https://github.com/mcxtzhang/SwipeDelMenuLayout
注意:如果侧滑删除要和角标拖拽一起使用,直接依赖该库会出现滑动冲突。需要做如下修改:

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        LogUtils.d(TAG, "dispatchTouchEvent() called with: " + "ev = [" + ev + "]"+isSwipeEnable);
        if (isSwipeEnable) {
            acquireVelocityTracker(ev);
            final VelocityTracker verTracker = mVelocityTracker;
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    isUserSwiped = false;//2016 11 03 add,判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。
                    isUnMoved = true;//2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。
                    iosInterceptFlag = false;//add by 2016 09 11 ,每次DOWN时,默认是不拦截的
                    if (isTouching) {//如果有别的指头摸过了,那么就return false。这样后续的move..等事件也不会再来找这个View了。
                        return false;
                    } else {
                        isTouching = true;//第一个摸的指头,赶紧改变标志,宣誓主权。
                    }

                    mLastP.set(ev.getRawX(), ev.getRawY());
                    mFirstP.set(ev.getRawX(), ev.getRawY());//2016 11 03 add,判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。

                    //如果down,view和cacheview不一样,则立马让它还原。且把它置为null
                    if (mViewCache != null) {
                        LogUtil.d("4");
                        if (mViewCache != this) {
                            mViewCache.smoothClose();

                            iosInterceptFlag = isIos;//add by 2016 09 11 ,IOS模式开启的话,且当前有侧滑菜单的View,且不是自己的,就该拦截事件咯。
                        }

                        //只要有一个侧滑菜单处于打开状态, 就不给外层布局上下滑动了
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }

                    //求第一个触点的id, 此时可能有多个触点,但至少一个,计算滑动速率用
                    mPointerId = ev.getPointerId(0);

                    break;
                case MotionEvent.ACTION_MOVE:
                    //add by 2016 09 11 ,IOS模式开启的话,且当前有侧滑菜单的View,且不是自己的,就该拦截事件咯。滑动也不该出现
                    if (iosInterceptFlag) {
                        break;
                    }
                    float gap = mLastP.x - ev.getRawX();
                    //为了在水平滑动中禁止父类ListView等再竖直滑动
                    if (Math.abs(gap) > 10 || Math.abs(getScrollX()) > 10) {//2016 09 29 修改此处,使屏蔽父布局滑动更加灵敏,
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    //2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。begin
                    if (Math.abs(gap) > mScaleTouchSlop) {
                        isUnMoved = false;
                    }
                    //2016 10 22 add , 仿QQ,侧滑菜单展开时,点击内容区域,关闭侧滑菜单。end
                    //如果scroller还没有滑动结束 停止滑动动画
/*                    if (!mScroller.isFinished()) {
                        mScroller.abortAnimation();
                    }*/
                    scrollBy((int) (gap), 0);//滑动使用scrollBy
                    //越界修正
                    if (isLeftSwipe) {//左滑
                        if (getScrollX() < 0) {
                            scrollTo(0, 0);
                        }
                        if (getScrollX() > mRightMenuWidths) {
                            scrollTo(mRightMenuWidths, 0);
                        }
                    } else {//右滑
                        if (getScrollX() < -mRightMenuWidths) {
                            scrollTo(-mRightMenuWidths, 0);
                        }
                        if (getScrollX() > 0) {
                            scrollTo(0, 0);
                        }
                    }

                    mLastP.set(ev.getRawX(), ev.getRawY());
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    //2016 11 03 add,判断手指起始落点,如果距离属于滑动了,就屏蔽一切点击事件。
                    if (Math.abs(ev.getRawX() - mFirstP.x) > mScaleTouchSlop) {
                        isUserSwiped = true;
                    }

                    //add by 2016 09 11 ,IOS模式开启的话,且当前有侧滑菜单的View,且不是自己的,就该拦截事件咯。滑动也不该出现
                    if (!iosInterceptFlag) {//且滑动了 才判断是否要收起、展开menu
                        //求伪瞬时速度
                        verTracker.computeCurrentVelocity(1000, mMaxVelocity);
                        final float velocityX = verTracker.getXVelocity(mPointerId);
                        if (Math.abs(velocityX) > 1000) {//滑动速度超过阈值
                            if (velocityX < -1000) {
                                if (isLeftSwipe) {//左滑
                                    //平滑展开Menu
                                    smoothExpand();

                                } else {
                                    //平滑关闭Menu
                                    smoothClose();
                                }
                            } else {
                                if (isLeftSwipe) {//左滑
                                    // 平滑关闭Menu
                                    smoothClose();
                                } else {
                                    //平滑展开Menu
                                    smoothExpand();

                                }
                            }
                        } else {
                            if (Math.abs(getScrollX()) > mLimit) {//否则就判断滑动距离
                                //平滑展开Menu
                                smoothExpand();
                            } else {
                                // 平滑关闭Menu
                                smoothClose();
                            }
                        }
                    }
       
                    releaseVelocityTracker();
                    isTouching = false;
                    break;
                default:
                    break;
            }
        } else {
            isTouching=false; //此处是处理冲突的位置,必须加
        }
        return super.dispatchTouchEvent(ev);
    }

3.消息角标自由拖拽
借鉴:https://github.com/qstumn/BadgeView
如果开启拖拽和滑动删除一起使用,上面冲突要处理,下面代码也要写

       badge.setOnDragStateChangedListener((dragState, badge1, targetView) -> {
      //拖拽成功和拖拽取消后开启侧滑  其他状态关闭侧滑  
  //必须调用smoothClose()
            if (dragState == Badge.OnDragStateChangedListener.STATE_SUCCEED) {
                ((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).smoothClose();
                ((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).setIos(true).setSwipeEnable(true);
            } else if (dragState == Badge.OnDragStateChangedListener.STATE_CANCELED) {
                ((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).smoothClose();
                ((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).setIos(true).setSwipeEnable(true);

            } else {
                ((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).smoothClose();
                ((SwipeMenuLayout) helper.getRecyclerViewHolder().itemView).setIos(false).setSwipeEnable(false);
            }
        });
        badge.setBadgeNumber(model.getCount());

4.可浮动的ImageView

@SuppressLint("AppCompatCustomView")
public class MoveImageView extends ImageView {

    private Drawable mDrawable;
    private int mLeft = 0;
    private int mTop = 0;
    private int mSpeed = 2;
    private boolean isSetVerticalMove;
    private boolean isMoveLeft;
    private boolean isMoveUp;
    private Handler mHandler;
    private int mCanvasBgSize;



    public MoveImageView(Context context) {
        super(context);
    }

    public MoveImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setUp(context, attrs);
    }

    public MoveImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setUp(context, attrs);
    }

    private void setUp(Context context, AttributeSet attrs) {
        TypedArray a =context.obtainStyledAttributes(attrs, R.styleable.MoveImage);
        int direction=a.getInteger(R.styleable.MoveImage_direction,0);
        mSpeed=a.getInteger(R.styleable.MoveImage_speed,2);

        if (direction == 0) {
            isSetVerticalMove = true;
        } else {
            isSetVerticalMove = false;
        }

        mDrawable = getDrawable();
        mHandler = new MoveHandler();
        mHandler.sendEmptyMessageDelayed(1, 220L);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (isSetVerticalMove) {
            canvas.translate(0.0F, mTop);
        } else {
            canvas.translate(mLeft, 0.0F);
        }
        mDrawable.draw(canvas);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        if (isSetVerticalMove) {
            mCanvasBgSize = getMeasuredHeight() * 3 / 2;
            mDrawable.setBounds(0, 0, getMeasuredWidth(), mCanvasBgSize);
        } else {
            mCanvasBgSize = getMeasuredWidth() * 3 / 2;
            mDrawable.setBounds(0, 0, mCanvasBgSize, getMaxHeight());
        }
    }

    private class MoveHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (isSetVerticalMove) {
                if (isMoveUp) {
                    if (mTop <= getMeasuredHeight() - mCanvasBgSize)//此时表示移到了最up的位置
                    {
                        mTop += mSpeed;
                        isMoveUp = false;
                    } else//继续下移
                    {
                        mTop -= mSpeed;
                    }
                } else {
                    if (mTop == 0)//此时表示移动到了最down,此时图片的up侧应该与屏幕up侧对齐,即坐标值为0
                    {
                        mTop -= mSpeed;
                        isMoveUp = true;//图片已经移动到了最down侧,需要修改其移动方向为up
                    } else {

                        mTop += mSpeed;//继续下移
                    }
                }
            } else {
                if (isMoveLeft)//向左移动
                {

                    if (mLeft <= getMeasuredWidth() - mCanvasBgSize)//此时表示移到了最左侧的位置
                    {
                        mLeft += mSpeed;
                        isMoveLeft = false;
                    } else//继续左移
                    {
                        mLeft -= mSpeed;
                    }

                } else {
                    if (mLeft == 0)//此时表示移动到了最右侧,此时图片的左侧应该与屏幕左侧对齐,即坐标值为0
                    {
                        mLeft -= mSpeed;
                        isMoveLeft = true;//图片已经移动到了最右侧,需要修改其移动方向为向左
                    } else {
                        mLeft += mSpeed;//继续右移
                    }
                }
            }
            invalidate();
            mHandler.sendEmptyMessageDelayed(1, 22);
        }
    }
}

5.讨论组头像展示
借鉴 https://github.com/jinyb09017/MutiImgLoader

6.仿IOS弹性scrollview

/**
 * created by dalang at 2018/11/21
 * 仿IOS 弹性scrollview
 */
public class ReboundScrollView extends NestedScrollView {

    //移动因子, 是一个百分比, 比如手指移动了100px, 那么View就只移动50px
    //目的是达到一个延迟的效果
    private static final float MOVE_FACTOR = 0.3f;

    //松开手指后, 界面回到正常位置需要的动画时间
    private static final int ANIM_TIME = 300;

    //ScrollView的子View, 也是ScrollView的唯一一个子View
    private View contentView;

    //手指按下时的Y值, 用于在移动时计算移动距离
    //如果按下时不能上拉和下拉, 会在手指移动时更新为当前手指的Y值
    private float startY;

    //用于记录正常的布局位置
    private Rect originalRect = new Rect();

    //手指按下时记录是否可以继续下拉
    private boolean canPullDown = false;

    //手指按下时记录是否可以继续上拉
    private boolean canPullUp = false;

    //在手指滑动的过程中记录是否移动了布局
    private boolean isMoved = false;


    public ReboundScrollView(Context context) {
        super(context);
    }


    public ReboundScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() > 0) {
            contentView = getChildAt(0);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        if(contentView == null) return;

        //ScrollView中的唯一子控件的位置信息, 这个位置信息在整个控件的生命周期中保持不变
        originalRect.set(contentView.getLeft(), contentView.getTop(), contentView
                .getRight(), contentView.getBottom());
    }

    /**
     * 在触摸事件中, 处理上拉和下拉的逻辑
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {

        if (contentView == null) {
            return super.dispatchTouchEvent(ev);
        }

        int action = ev.getAction();

        switch (action) {
            case MotionEvent.ACTION_DOWN:

                //判断是否可以上拉和下拉
                canPullDown = isCanPullDown();
                canPullUp = isCanPullUp();

                //记录按下时的Y值
                startY = ev.getY();
                break;

            case MotionEvent.ACTION_UP:

                if(!isMoved) break;  //如果没有移动布局, 则跳过执行

                // 开启动画
                TranslateAnimation anim = new TranslateAnimation(0, 0, contentView.getTop(),
                        originalRect.top);
                anim.setDuration(ANIM_TIME);

                contentView.startAnimation(anim);

                // 设置回到正常的布局位置
                contentView.layout(originalRect.left, originalRect.top,
                        originalRect.right, originalRect.bottom);

                //将标志位设回false
                canPullDown = false;
                canPullUp = false;
                isMoved = false;

                break;
            case MotionEvent.ACTION_MOVE:

                //在移动的过程中, 既没有滚动到可以上拉的程度, 也没有滚动到可以下拉的程度
                if(!canPullDown && !canPullUp) {
                    startY = ev.getY();
                    canPullDown = isCanPullDown();
                    canPullUp = isCanPullUp();

                    break;
                }

                //计算手指移动的距离
                float nowY = ev.getY();
                int deltaY = (int) (nowY - startY);

                //是否应该移动布局
                boolean shouldMove =
                        (canPullDown && deltaY > 0)    //可以下拉, 并且手指向下移动
                                || (canPullUp && deltaY< 0)    //可以上拉, 并且手指向上移动
                                || (canPullUp && canPullDown); //既可以上拉也可以下拉
                            (这种情况出现在ScrollView包裹的控件比ScrollView还小)

                if(shouldMove){
                    //计算偏移量
                    int offset = (int)(deltaY * MOVE_FACTOR);

                    //随着手指的移动而移动布局
                    contentView.layout(originalRect.left, originalRect.top + offset,
                            originalRect.right, originalRect.bottom + offset);

                    isMoved = true;  //记录移动了布局
                }

                break;
            default:
                break;
        }

        return super.dispatchTouchEvent(ev);
    }


    /**
     * 判断是否滚动到顶部
     */
    private boolean isCanPullDown() {
        return getScrollY() == 0 ||
                contentView.getHeight() < getHeight() + getScrollY();
    }

    /**
     * 判断是否滚动到底部
     */
    private boolean isCanPullUp() {
        return  contentView.getHeight() <= getHeight() + getScrollY();
    }
}

其他文章链接地址:
(一)高斯模糊实现毛玻璃效果丶共享元素动画 丶地址选择器
(二)仿京东顶部伸缩渐变丶自定义viewpager指示器丶viewpager3D回廊丶recyclerview瀑布流
(三)RxJava2常用操作符merge、flatmap、zip--结合MVP架构讲解
(四)仿支付宝首页顶部伸缩滑动/中间层下拉刷新
(五)TabLayout+ViewPager悬浮吸顶及刷新数量动画显示
(七)仿微信发布朋友圈拖拽删除

将持续更新.. 不喜勿喷,仅个人分享,希望能帮助到你

源码地址:Github传送门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,192评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,186评论 1 303
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,844评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,471评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,876评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,891评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,068评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,791评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,539评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,772评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,250评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,577评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,244评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,146评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,949评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,995评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,812评论 2 276