码迷,mamicode.com
首页 > 其他好文 > 详细

ViewDragHelper的妙用二 --QQ侧滑菜单的实现

时间:2015-08-05 14:57:27      阅读:150      评论:0      收藏:0      [点我收藏+]

标签:qq侧滑菜单   viewdraghelper   

好了,还是老规矩,先给出效果图,这里就绘制了一个简单框架,各位看官可以任意添加自己的东西。


技术分享


下面我来解释一下怎么使用我们的ViewDragHelper来实现这个效果

先给出我们的布局

<?xml version="1.0" encoding="UTF-8"?>
<com.jeason.qqmenudemo.widget.SideslipLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main_frame"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/bg" >

    <FrameLayout
        android:id="@+id/left_panel"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
        <TextView 
            android:ems="1"
            android:layout_height="match_parent"
            android:layout_width="wrap_content"
            android:textColor="#FFFFFF"
            android:layout_marginTop="56dp"
            android:textSize="24dp"
            android:layout_gravity="center"
            android:text="@string/menu_block"
            />
    </FrameLayout>

    <com.jeason.qqmenudemo.widget.MyFrameLayout
        android:id="@+id/main_panel"
        android:background="@drawable/bg_3"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" >

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

            <FrameLayout
                android:id="@+id/content"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
                <com.jeason.qqmenudemo.widget.MyTextView 
                    android:id="@+id/content_block"
                    android:layout_width="260dp"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    android:text="@string/content_bolock"
                    android:padding="8dp"
                    android:textSize="18dp"
                    />
            </FrameLayout>
                
        </LinearLayout>
    </com.jeason.qqmenudemo.widget.MyFrameLayout>

</com.jeason.qqmenudemo.widget.SideslipLayout>

我们的布局文件很简单,就是自定义了一个侧滑布局SideslipLayout,然后我们将里面的内容分为2块LeftPanel(菜单区)和MainPanel(主内容区)。

我们目前认为所有的童鞋对于ViewDragHelper都有一定的了解,不了解的可以看一下我的上一篇博文。

强大的ViewDragHelper和ViewDragHelper的妙用 一

这里,我们先给出代码,再给大家慢慢解释。


package com.jeason.qqmenudemo.widget;

import com.jeason.qqmenudemo.R;
import com.jeason.qqmenudemo.view.ViewHelper;

import android.annotation.SuppressLint;
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;

public class SideslipLayout extends FrameLayout{

    private static final String TAG = "SideslipLayout";
    
    @SuppressWarnings("unused")
    private Context mContext;
    
    private ViewDragHelper mDragHelper;
    private DragListener mDragListener;
    private int range;
    private int width;
    private int height;
    private int mainLeft;

    private FrameLayout mLeftPanel;
    private MyFrameLayout mMainPanel;
    
    private Status mDefaultStatus = Status.Close;
    private Status status = mDefaultStatus;
    
    public enum Status {
        Drag, Open, Close
    }
    public SideslipLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
        mContext = context;
        mDragHelper = ViewDragHelper.create(this, mDragHelperCallback);
    }

    @Override
    protected void onFinishInflate() {
        mMainPanel = (MyFrameLayout) findViewById(R.id.main_panel);
        mMainPanel.setDragLayout(this);
        
        mLeftPanel = (FrameLayout) findViewById(R.id.left_panel);
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        
        width = mLeftPanel.getMeasuredWidth(); //which meaning Full_Window
        height = mLeftPanel.getMeasuredHeight();
        
        range = (int) (width * 0.6f);
        
    }    
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        try {
            mDragHelper.processTouchEvent(e);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return true;
    }
    
    
    public Status getStatus() {
        if (mainLeft == 0) {
            status = Status.Close;
        } else if (mainLeft == range) {
            status = Status.Open;
        } else {
            status = Status.Drag;
        }
        return status;
    }
    
    public void open() {
        open(true);
    }

    public void open(boolean animate) {
        if (animate) {
            if (mDragHelper.smoothSlideViewTo(mMainPanel, range, 0)) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        } else {
            mMainPanel.layout(range, 0, range * 2, height);
            dispatchDragEvent(range);
        }
    }

    public void close() {
        close(true);
    }

    public void close(boolean animate) {
        if (animate) {
            if (mDragHelper.smoothSlideViewTo(mMainPanel, 0, 0)) {
                ViewCompat.postInvalidateOnAnimation(this);
            }
        } else {
            mMainPanel.layout(0, 0, width, height);
            dispatchDragEvent(0);
        }
    }
    
    private void dispatchDragEvent(int mainLeft) {
        if (mDragListener == null) {
            return;
        }
        float percent = mainLeft / (float) range;
        animateView(percent);
        mDragListener.onDrag(percent);
        Status lastStatus = status;
        if (lastStatus != getStatus() && status == Status.Close) {
            mDragListener.onClose();
        } else if (lastStatus != getStatus() && status == Status.Open) {
            mDragListener.onOpen();
        }
    }
    
    private void animateView(float percent) {
        float f1 = 1 - percent * 0.3f;
        ViewHelper.setScaleX(mMainPanel, f1);
        ViewHelper.setScaleY(mMainPanel, f1);
        ViewHelper.setTranslationX(mLeftPanel, -mLeftPanel.getWidth() / 2.3f + mLeftPanel.getWidth() / 2.3f * percent);
        ViewHelper.setScaleX(mLeftPanel, 0.5f + 0.5f * percent);
        ViewHelper.setScaleY(mLeftPanel, 0.5f + 0.5f * percent);
        ViewHelper.setAlpha(mLeftPanel, percent);
    }
    
    @Override
    public void computeScroll() {
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }
    
    public interface DragListener {
        public void onOpen();

        public void onClose();

        public void onDrag(float percent);
    }
    
    public void setDragListener(DragListener dragListener) {
        mDragListener = dragListener;
    }    
    
    private ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() {

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if (mainLeft + dx < 0) {
                return 0;
            } else if (mainLeft + dx > range) {
                return range;
            } else {
                return left;
            }
        }

        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return true;
        }

        @Override
        public int getViewHorizontalDragRange(View child) {
            return range;
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            if (xvel > 0) {
                open();
            } else if (xvel < 0) {
                close();
            } else if (releasedChild == mMainPanel && mainLeft > range * 0.3) {
                open();
            } else if (releasedChild == mLeftPanel && mainLeft > range * 0.7) {
                open();
            } else {
                close();
            }
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top,
                int dx, int dy) {
            if (changedView == mMainPanel) {
                mainLeft = left;
            } else {
                mainLeft = mainLeft + left;
            }
            if (mainLeft < 0) {
                mainLeft = 0;
            } else if (mainLeft > range) {
                mainLeft = range;
            }

            if (changedView == mLeftPanel) {
                mLeftPanel.layout(0, 0, width, height);
                mMainPanel.layout(mainLeft, 0, mainLeft + width, height);
            }

            dispatchDragEvent(mainLeft);
        }
    };
}

有我的上一篇文章我们知道,任何特效的View都最终是要通过onInterceptTouchEvent和onTouchEvent来实现的,所以首先我们需要将我们的MotionEvent交给我们的ViewDragHelper 实现

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return mDragHelper.shouldInterceptTouchEvent(ev);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        try {
            mDragHelper.processTouchEvent(e);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return true;
    }
这里,我们一般最好采用OnTouchEvent中return true;当然如果我们的ChildView有可能需要消耗MotionEvent,那么我们需要在ChildView的OnTouchEvent 中Action_Down 中return true;(如果ChildView在Action_Down return false ;那么最终 MotionEvent走到ParentView always return true;那么ChildView 将无法得到后续的MotionEvent);

当然,考虑另一种情况,如果我们持续让我们的ParentView 中return false;那么我们可以假设如果此次的MotionEvent 最终没有被消耗(所有的ChildView都 return false ,那么我们的ParentView最终会得到Action_Down但是由于我们在Action_Down 返回了false;name 在得到Action_Down之后不会继续得到Action_UP(这里假设我们的ParentVIew是最上层ViewGroup),那么dispatchTouchEvent返回了false,事件被取消了,那么你会发现我们的ChildView不能够再移动了),这里可以说是ViewDragHelper需要考虑的逻辑问题。

 当让如果我们需要我们的ChildView可能也要Action_Move时,那么就需要我们严格把控onInterceptTouchEvent,因为如果我们直接将我们的MotionEvent交给我们的ViewDragHelper,在这种情况下,因为我们的ChildView是可以移动的,所以onInterceptTouchEvent直接就返回了true,ChildView就没有机会了。那么我们可以怎么做呢。比如

 

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) { 
        boolean result;
        if(ev.getAction() == MotionEvent.ACTION_MOVE){
            if(mChildView.requestTouchEvent(ev)){
                result = false;
            }else{
                result = mDragHelper.shouldInterceptTouchEvent(ev);
            }
        }else{
            result = mDragHelper.shouldInterceptTouchEvent(ev);
        }
        
        return result;
    }

 这里仅仅给大家做一个说明,避免复杂情况下大家发现我们的ViewDragHelper出现各种不是那么灵敏的情形,或者不是那么容易掌控。反正这里还是要提醒大家一定要充分理解onInterceptTouchEvent和onTouchEvent和dispatchTouchEvent之间的逻辑关系。

  好的,走到这里我们才完成了将我们的移动处理逻辑交给了ViewDragHelper。按照我上次分析的我们的移动实现要交给我们的CallBack

 private ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() {

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            if (mainLeft + dx < 0) {        //计算ChildView左边界
                return 0;
            } else if (mainLeft + dx > range) {
                return range;
            } else {
                return left;
            }
        }

        @Override
        public boolean tryCaptureView(View child, int pointerId) {   //这里表示我们的Left和Main都可以移动
            return true;
        }

        @Override
        public int getViewHorizontalDragRange(View child) { //框定水平移动范围
            return range;
        }

        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) { //移动结束后判断开合状态自动切换状态
            super.onViewReleased(releasedChild, xvel, yvel);
            if (xvel > 0) {
                open();
            } else if (xvel < 0) {
                close();
            } else if (releasedChild == mMainPanel && mainLeft > range * 0.3) {
                open();
            } else if (releasedChild == mLeftPanel && mainLeft > range * 0.7) {
                open();
            } else {
                close();
            }
        }

        @Override
        public void onViewPositionChanged(View changedView, int left, int top,
                int dx, int dy) {   
            if (changedView == mMainPanel) {
                mainLeft = left;
            } else {
                mainLeft = mainLeft + left;
            }
            if (mainLeft < 0) {
                mainLeft = 0;
            } else if (mainLeft > range) {
                mainLeft = range;
            }

            if (changedView == mLeftPanel) { //移动Left时候,保持Left不动改变Main的位置
                mLeftPanel.layout(0, 0, width, height);
                mMainPanel.layout(mainLeft, 0, mainLeft + width, height);
            }

            dispatchDragEvent(mainLeft); //开启移动的时候的动画效果缩放等
        }
    };


分析完了CallBack,好像就没有什么说的了,源码下载

http://download.csdn.net/download/jaysong2012/8961751


下面继续给大家分享的更复杂一点的ViewDragHelper的妙用,有兴趣的话大家可以尝试一下。重点提示一下在onInterceptTouchEvent中的处理,有机会的话再把源码共享给大家。


技术分享

版权声明:本文为博主原创文章,未经博主允许不得转载。

ViewDragHelper的妙用二 --QQ侧滑菜单的实现

标签:qq侧滑菜单   viewdraghelper   

原文地址:http://blog.csdn.net/jaysong2012/article/details/47253799

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!