QQ5.0的側滑效果有多種實現方式,android
如http://blog.csdn.net/lmj623565791/article/details/39257409 就是利用HorizontalScrollView實現的,簡單實用;ide
如http://blog.csdn.net/manoel/article/details/39013095/ 經過改造SlidingMenu實現,沒有改變原有SlidingMenu功能,屏幕邊緣側滑也能夠....佈局
相對來講ViewDragHelper實現方式最爲複雜,但靈活性也更高,能夠應對各類需求,畢竟google的DrawerLayout也是用ViewDragHelper實現的。post
先看下效果:動畫
分析:主面板有個ViewPager,須要注意的是viewpager的滑動確定與側滑相互衝突,通常咱們讓viewpager的第一頁是能夠向右拖出側滑菜單,其餘頁則響應viewpager的滑動。this
代碼:google
自定義側滑控件DragLayout ,代碼量很多,不過關鍵地方都加了註釋。lua
package com.liujing.draglayoutdemo;/** * 經過ViewDragHelper實現的側滑控件 * @author liujing * */ public class DragLayout extends FrameLayout { private View mLeftContent; private View mMainContent; private int mWidth; private int mDragRange; private ViewDragHelper mDragHelper; private int mMainLeft; private int mHeight; private Status mStatus = Status.Close; private GestureDetectorCompat mDetectorCompat; public DragLayout(Context context) { this(context, null); } public DragLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DragLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); //ViewDragHelper.create(forParent, sensitivity, cb); //對應參數:父佈局、敏感度、回調 mDragHelper = ViewDragHelper.create(this, mCallBack); mDetectorCompat = new GestureDetectorCompat(getContext(), mGestureListener); } private boolean isDrag = true; public void setDrag(boolean isDrag) { this.isDrag = isDrag; if(isDrag){ //這裏有個Bug,當isDrag從false變爲true是,mDragHelper的mCallBack在 //首次滑動時不響應,再次滑動才響應,只好在此調用下,讓mDragHelper恢復下狀態 mDragHelper.abort(); } } SimpleOnGestureListener mGestureListener = new SimpleOnGestureListener() { public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
if((Math.abs(distanceX) > Math.abs(distanceY))&&distanceX<0&&isDrag!=false&&mStatus==Status.Close){ return true; }else if((Math.abs(distanceX) > Math.abs(distanceY))&&distanceX>0&&isDrag!=false&&mStatus==Status.Open){ return true; }else { return false; } }; }; ViewDragHelper.Callback mCallBack = new ViewDragHelper.Callback() { public void onEdgeTouched(int edgeFlags, int pointerId) { }; public void onEdgeDragStarted(int edgeFlags, int pointerId) { mDragHelper.captureChildView(mMainContent, pointerId); }; // 決定child是否可被拖拽。返回true則進行拖拽。 @Override public boolean tryCaptureView(View child, int pointerId) { return child == mMainContent || child == mLeftContent; } // 當capturedChild被拖拽時 @Override public void onViewCaptured(View capturedChild, int activePointerId) { super.onViewCaptured(capturedChild, activePointerId); } // 橫向拖拽的範圍,大於0時可拖拽,等於0沒法拖拽 // 此方法只用於計算如view釋放速度,敏感度等 // 實際拖拽範圍由clampViewPositionHorizontal方法設置 @Override public int getViewHorizontalDragRange(View child) { return mDragRange; } // 此處設置view的拖拽範圍。(實際移動還未發生) @Override public int clampViewPositionHorizontal(View child, int left, int dx) { // 拖動前oldLeft + 變化量dx == left if (mMainLeft + dx < 0) { return 0; } else if (mMainLeft + dx > mDragRange) { return mDragRange; } return left; } // 決定了當View位置改變時,但願發生的其餘事情。(此時移動已經發生) // 高頻實時的調用,在這裏設置左右面板的聯動 @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { //若是拖動的是主面板 if (changedView == mMainContent) { mMainLeft = left; } else { mMainLeft += dx; } // 進行值的修正 if (mMainLeft < 0) { mMainLeft = 0; } else if (mMainLeft > mDragRange) { mMainLeft = mDragRange; } // 若是拖拽的是左面板,強制在指定位置繪製Content if (changedView == mLeftContent) { layoutContent(); } dispatchDragEvent(mMainLeft); } // View被釋放時,側滑打開或恢復 @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { if (xvel > 0) { open(); } else if (xvel == 0 && mMainLeft > mDragRange * 0.5f) { open(); } else { close(); } } //當拖拽狀態改變的時,IDLE/DRAGGING/SETTLING @Override public void onViewDragStateChanged(int state) { super.onViewDragStateChanged(state); } }; private void layoutContent() { mMainContent.layout(mMainLeft, 0, mMainLeft + mWidth, mHeight); mLeftContent.layout(0, 0, mWidth, mHeight); } /** * 每次更新都會調用 根據當前執行的位置計算百分比percent */ protected void dispatchDragEvent(int mainLeft) { float percent = mainLeft / (float) mDragRange; animViews(percent); if (mListener != null) { mListener.onDraging(percent); } Status lastStatus = mStatus; if (updateStatus(mainLeft) != lastStatus) { if (mListener == null) { return; } if (lastStatus == Status.Draging) { if (mStatus == Status.Close) { mListener.onClose(); } else if (mStatus == Status.Open) { mListener.onOpen(); } } } } public static interface OnLayoutDragingListener { void onOpen(); void onClose(); void onDraging(float percent); } private OnLayoutDragingListener mListener; public void setOnLayoutDragingListener(OnLayoutDragingListener l) { mListener = l; } private Status updateStatus(int mainLeft) { if (mainLeft == 0) { mStatus = Status.Close; } else if (mainLeft == mDragRange) { mStatus = Status.Open; } else { mStatus = Status.Draging; } return mStatus; } public static enum Status { Open, Close, Draging } public Status getStatus() { return mStatus; } public void setStatus(Status mStatus) { this.mStatus = mStatus; } /** * 伴隨動畫: * @param percent */ private void animViews(float percent) { // 主面板:縮放 float inverse = 1 - percent * 0.2f; ViewHelper.setScaleX(mMainContent, inverse); ViewHelper.setScaleY(mMainContent, inverse); // 左面板:縮放、平移、透明度 ViewHelper.setScaleX(mLeftContent, 0.5f + 0.5f * percent); ViewHelper.setScaleY(mLeftContent, 0.5f + 0.5f * percent); ViewHelper.setTranslationX(mLeftContent, -mWidth / 2.0f + mWidth / 2.0f * percent); ViewHelper.setAlpha(mLeftContent, percent); // 背景:顏色漸變 getBackground().setColorFilter( evaluate(percent, Color.BLACK, Color.TRANSPARENT), PorterDuff.Mode.SRC_OVER); } private int evaluate(float fraction, int startValue, int endValue) { int startInt = (Integer) startValue; int startA = (startInt >> 24) & 0xff; int startR = (startInt >> 16) & 0xff; int startG = (startInt >> 8) & 0xff; int startB = startInt & 0xff; int endInt = (Integer) endValue; int endA = (endInt >> 24) & 0xff; int endR = (endInt >> 16) & 0xff; int endG = (endInt >> 8) & 0xff; int endB = endInt & 0xff; return (int) ((startA + (int) (fraction * (endA - startA))) << 24) | (int) ((startR + (int) (fraction * (endR - startR))) << 16) | (int) ((startG + (int) (fraction * (endG - startG))) << 8) | (int) ((startB + (int) (fraction * (endB - startB)))); } @Override public boolean onInterceptTouchEvent(android.view.MotionEvent ev) { boolean onTouchEvent = mDetectorCompat.onTouchEvent(ev); //將Touch事件傳遞給ViewDragHelper return mDragHelper.shouldInterceptTouchEvent(ev) & onTouchEvent; }; @Override public boolean onTouchEvent(MotionEvent event) { try { //將Touch事件傳遞給ViewDragHelper mDragHelper.processTouchEvent(event); } catch (Exception e) { } return true; } public void close() { close(true); }; public void open() { open(true); } public void close(boolean isSmooth) { mMainLeft = 0; if (isSmooth) { // 執行動畫,返回true表明有未完成的動畫, 須要繼續執行 if (mDragHelper.smoothSlideViewTo(mMainContent, mMainLeft, 0)) { // 注意:參數傳遞根ViewGroup ViewCompat.postInvalidateOnAnimation(this); } } else { layoutContent(); } } public void open(boolean isSmooth) { mMainLeft = mDragRange; if (isSmooth) { if (mDragHelper.smoothSlideViewTo(mMainContent, mMainLeft, 0)) { ViewCompat.postInvalidateOnAnimation(this); } } else { layoutContent(); } } @Override public void computeScroll() { // 高頻率調用,決定是否有下一個變更等待執行 if (mDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { mMainContent.layout(mMainLeft, 0, mMainLeft + mWidth, mHeight); mLeftContent.layout(0, 0, mWidth, mHeight); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); //拿到寬高 mWidth = getMeasuredWidth(); mHeight = getMeasuredHeight(); //設置拖動範圍 mDragRange = (int) (mWidth * 0.6f); } /** * 填充結束時得到兩個子佈局的引用 */ @Override protected void onFinishInflate() { int childCount = getChildCount(); // 必要的檢驗 if (childCount < 2) { throw new IllegalStateException( "You need two childrens in your content"); } if (!(getChildAt(0) instanceof ViewGroup) || !(getChildAt(1) instanceof ViewGroup)) { throw new IllegalArgumentException( "Your childrens must be an instance of ViewGroup"); } mLeftContent = getChildAt(0); mMainContent = getChildAt(1); } }
我設置了一個isDrag的標籤來控制是否容許側滑;spa
在SimpleOnGestureListener的onScroll方法中判斷,若是是橫向向右滑動,且側滑是關閉狀態,且isDrag的tag爲true時,讓ViewDragHelper響應對應滑動事件(滑出),.net
若是是橫向向左滑動,且側滑是開啓狀態,且isDrag的tag爲true時,讓ViewDragHelper響應對應滑動事件(滑入),
其他狀況,都不處理;
在onViewPositionChanged裏經過dispatchDragEvent方法,計算移動百分比,據此執行相應的伴隨動畫,同時也將該值經過回調傳遞到外面,執行動畫用了nineoldandroids來兼容以前版本。
MainContentLayout是爲了處理當側滑菜單打開後,主面板便再也不響應內部的Touch事件了。
package com.liujing.draglayoutdemo;public class MainContentLayout extends RelativeLayout { private DragLayout mDragLayout; public MainContentLayout(Context context, AttributeSet attrs) { super(context, attrs); } public MainContentLayout(Context context) { super(context); } public void setDragLayout(DragLayout mDragLayout) { this.mDragLayout = mDragLayout; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if(mDragLayout.getStatus() == Status.Close){ return super.onInterceptTouchEvent(ev); }else { return true; } } @Override public boolean onTouchEvent(MotionEvent event) { if(mDragLayout.getStatus() == Status.Close){ return super.onTouchEvent(event); }else { if(MotionEventCompat.getActionMasked(event) == MotionEvent.ACTION_UP){ mDragLayout.close(); } return true; } } }
DragLayout設置回調監聽:
mDragLayout.setOnLayoutDragingListener(new OnLayoutDragingListener() { @Override public void onOpen() { //打開 } @Override public void onDraging(float percent) { //滑動中 } @Override public void onClose() { //關閉 } });
當ViewPager切換時,只要給DragLayout設置是否容許側滑便可
public void onPageSelected(int postion) { switch (postion) { case 0: mDragLayout.setDrag(true); break; case 1: mDragLayout.setDrag(false); break; case 2: mDragLayout.setDrag(false); break; } }
佈局示例:
<com.liujing.draglayoutdemo.DragLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/dl" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/img_frame_background" > <LinearLayout android:id="@+id/fl_menu" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="40dp" android:paddingLeft="10dp" android:paddingTop="50dp" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#ffffff" android:textSize="18sp" android:text="這是左面板" > </TextView> </LinearLayout> <com.liujing.draglayoutdemo.MainContentLayout android:id="@+id/mainContent" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/tab_bg" android:orientation="vertical" > <android.support.v4.view.ViewPager android:id="@+id/pager_view" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_below="@id/topbar" /> </com.liujing.draglayoutdemo.MainContentLayout> </com.liujing.draglayoutdemo.DragLayout>
Demo下載:http://files.cnblogs.com/files/liujingg/DragLayoutDemo.rar