隨著Android的不斷成熟,許多絢麗的效果也在不斷的被大家開發出來,其中側滑的效果用到的項目很多,用的好的更是給吸引了很多用戶。國內像QQ和酷狗App的側滑就很給力,所以查了一些資料,并結合ViewDragHelper輔助類,做了一種比較簡單的側滑實現方式。
一、實現效果圖
- 實現的效果基本跟酷狗App差不多,因為就是仿造酷狗的~~
二、實現原理
SlideLayout控件使用的是ViewDragHelper輔助類來實現的。ViewDragHelper是一個實現View的拖拽的神器,它把View的拖拽操作變得特別的簡單,不熟悉ViewDragHelper的同學請先上傳送門。
要實現拖拽,首先需要將SlideLayout和ViewDragHelper關聯起來,然后將SlideLayout的事件交給ViewDragHelper來處理,然后在ViewDragHelper提供的回調里就可以對View進行各種操作。不過拖拽的原理都是差不多的,通過水平或者豎直的移動ViewGroup,然后不斷的layout和invalidate進行重繪顯示。
在滑動的過程中,除了要不斷的計算滑動的位置和重繪界面,還需要對子容器進行不同的動畫操作,這里使用的是ViewHelper類對View做平移縮放和漸變等動畫。
另外還使用枚舉來記錄SlideLayout側滑的狀態,包括關閉,打開和正在滑動。并且提供PanelSlideListener監聽滑動的狀態。這樣就可以根據不同的狀態做不同的操作。比如手動打開側滑,關閉側滑等等。
三、邏輯分析
這個項目實現的邏輯其實并不難,只需要計算出ViewGroup滑動的位置,然后重繪就行,其次還需要計算控件縮放和拉伸的比例等等。當然對各種View的操作方法還是要比較熟悉,不然搞不明白有些邏輯要做這里做。
1. SlideLayout應該作為一個控件容器來包容兩個子容器,一個菜單容器,一個主容器,首先我們需要獲取SlideLayout容器的寬高和兩個子容器對象
-
在View的onSizeChanged()方法里獲取SlideLayout的寬高,此時控件已經測量完成
/** * 當控件的寬高發生變化時會回調這個方法,可以用來測量控件的寬高 * * @param w * @param h * @param oldw * @param oldh */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mSlideHeight = getMeasuredHeight(); mSlideWidth = getMeasuredWidth(); /** * 初始化拖動的范圍 * 默認為屏幕寬度的60% */ mSlideRange = (int) (mSlideWidth * mRangePercent); }
-
在View的onFinishInflate()方法里可以獲取容器對象,此時布局已經填充
/** * 當View填充結束時會調用這個方法,可以獲取子View對象 */ @Override protected void onFinishInflate() { super.onFinishInflate(); if (getChildCount() < 2) { throw new IllegalStateException("SlideLayout控件的子View必須大于2個"); } if (!((getChildAt(0) instanceof ViewGroup) && (getChildAt(1) instanceof ViewGroup))) { throw new IllegalArgumentException("SlideLayout控件的子View必須是ViewGroup"); } mMenuContainer = (ViewGroup) getChildAt(0); mMainContainer = (ViewGroup) getChildAt(1); }
2. 獲取到了需要的屬性和對象之后,就可以將SlideLayout和ViewDragHelper進行綁定
-
首先在控件的構造里創建ViewDragHelper對象,創建完之后會有一個回調,而我們對View的各種操作就是在回調的各種方法里進行的
/** View的滑動的輔助類,在回調里監聽View的各種操作 * @param forParent 要進行觸摸滑動的父控件 * @param sensitivity 控件滑動的速度,敏感度,1.0f正常 * @param cb 對View的事件發生改變的回調 */ mDragHelper = ViewDragHelper.create(this, 1.0f, mViewCallback);
-
創建對象之后,如果此時就對View進行操作是沒有效果的,因為還需要把SlideLayout的處理事件傳遞給ViewDragHelper
/** * 轉交攔截事件給輔助類 * * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { final int action = MotionEventCompat.getActionMasked(ev); if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { mDragHelper.cancel(); return false; } return mDragHelper.shouldInterceptTouchEvent(ev); } /** * 轉交觸摸事件給輔助類 * * @param event * @return */ @Override public boolean onTouchEvent(MotionEvent event) { try { mDragHelper.processTouchEvent(event); } catch (Exception e) { e.printStackTrace(); } return true; }
-
最重要的地方就是ViewDragHelper的回調了,里面有很多方法,每一個都很重要,這里列舉一個對容器的滑動處理方法onViewPositionChanged()。其實邏輯也是比較簡單,就是判斷當前滑動的是哪一個容器,計算容器的左邊界值,然后對容器進行重繪
/** * 當子View的位置發送改變時回調 * @param changedView 改變的子View * @param left 距離左邊界距離 * @param top 距離頂部距離 * @param dx 水平滑動距離差 * @param dy 豎直滑動距離差 */ @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { /** * 將菜單面板的移動量給主面板 */ if (changedView == mMenuContainer) { mMenuContainer.layout(0, 0, mSlideWidth, mSlideHeight); int newLeft = mMainContainer.getLeft() + dx; newLeft = fixLeft(newLeft); mMainContainer.layout(newLeft, 0, newLeft + mSlideWidth, mSlideHeight); } // 處理移動事件 performSlideEvent(); }
五、使用教程
-
布局文件中
<com.pinger.slide.SlideLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/slideLayout" android:layout_width="match_parent" android:background="@mipmap/icon_bg" android:layout_height="match_parent"> // 菜單容器 <include layout="@layout/layout_menu"/> // 主容器 <include layout="@layout/layout_main"/> </com.pinger.slide.SlideLayout>
代碼中獲取對象,設置監聽,設置打開或者關閉側滑
六、總結
有了ViewDragHelper這個輔助類,對ViewGroup進行操作相對來說已經比較簡單了,只需要處理計算和繪制的工作,其他的都已經做好了。當然ViewDragHelper的作用遠不于此,想要了解更多的同學可以去研究一下這個類的源碼。這里也只是簡單的實現了側滑功能,要想做的更完美的同學請自行修改。