自定義ViewGroup通常需要重寫onMeasure()、onLayout()、onTouchEvent()方法。本文實作一個繼承自ScrollView的自定義ViewGroup,監聽手勢添加一個慣性及回彈效果。
1、首先重寫onMeasure()方法來測量ViewGroup的子View,周遊ViewGroup的子View,并調用measureChild()方法進行測量。
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int count = getChildCount();
- for (int i = 0; i < count; ++i) {
- View childView = getChildAt(i);
- measureChild(childView,
- widthMeasureSpec, heightMeasureSpec);
- }
- }
2、測量完子View需要對其進行擺放,即設定子View在ViewGroup的位置。這裡将子View設為全屏便于滑動操作,一般需要根據子View的屬性WrapContent/FillParent來确定其布局位置。
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int childCount = getChildCount();
- // 設定ViewGroup的高度
- MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
- mlp.height = mScreenHeight * childCount;
- setLayoutParams(mlp);
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child.getVisibility() != View.GONE) {
- child.layout(l, i * mScreenHeight,
- r, (i + 1) * mScreenHeight);
- }
- }
- }
3、監聽手勢,重寫onTouchEvent()事件
- @Overridepublic
- boolean onTouchEvent(MotionEvent event) {
- int y = (int) event.getY();
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN: //手指放下,記錄放下y坐标及View滑動y坐标
- mLastY = y;
- mStart = getScrollY();
- break;
- case MotionEvent.ACTION_MOVE: //手指移動,
- if (!mScroller.isFinished()) { //滑動動畫未結束,則終止動畫
- mScroller.abortAnimation();
- }
- int dy = mLastY - y;
- //若已經到頂部且手指下滑,則不進行位移動畫,即到頂不允許下拉動畫
- if (getScrollY() < 0) {
- dy = 0;
- }
- //到底不允許上拉動畫
- if (getScrollY() > getHeight() - mScreenHeight) {
- dy = 0;
- }
- scrollBy(0, dy);//View随手勢滑動
- mLastY = y;
- break;
- case MotionEvent.ACTION_UP://手指松開
- mEnd = getScrollY();
- int dScrollY = mEnd - mStart;
- if (dScrollY > 0) {//手勢向上滑動
- if (dScrollY < mScreenHeight / 3) {//滑動距離未超過螢幕1/3,則回彈
- mScroller.startScroll( 0, getScrollY(), 0, -dScrollY);
- } else {//滑動超過1/3,繼續慣性滑動剩餘距離
- mScroller.startScroll(0, getScrollY(), 0, mScreenHeight - dScrollY);
- }
- } else {//手勢向下滑動
- if (-dScrollY < mScreenHeight / 3) {
- mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
- } else {
- mScroller.startScroll( 0, getScrollY(),0, -mScreenHeight - dScrollY);
- }
- }
- break;
- }
- postInvalidate();//更新UI,顯示動畫
- return true;
- }
4、重寫computeScroll(),ViewGroup滑動到指定位置
- @Override
- public void computeScroll() {
- super.computeScroll();
- if (mScroller.computeScrollOffset()) {
- scrollTo(0, mScroller.getCurrY());
- postInvalidate();
- }
- }
5、初始化View
- private void initView(Context context) {
- WindowManager wm = (WindowManager) context.getSystemService(
- Context.WINDOW_SERVICE);
- DisplayMetrics dm = new DisplayMetrics();
- wm.getDefaultDisplay().getMetrics(dm);
- mScreenHeight = dm.heightPixels;//擷取目前手機螢幕高度
- mScroller = new Scroller(context);//初始化Scroller輔助類
- }
6、startScroll方法API
- public void startScroll (int startX, int startY, int dx, int dy)
以提供的起始點和将要滑動的距離開始滾動,預設持續時間250ms。控制ViewGroup的内容進行滑動而非ViewGroup本身,且dx正值表示左滑,負值表示右滑。
參數:
startX 目前水準方向滑動偏移值,即目前ScrollY值,正值表明向左滑
startY 目前垂直方向滑動偏移值,即目前ScrollX值,正值表明向上滑
dx 水準方向滑動距離,正值表明向左滑動
dy 垂直方向滑動距離,正值表明向上滑動