Android自定义ViewGroup

Android自定义ViewGroup

以Dome为例,来一步一步的讲解如何自定义ViewGoup,先看下面Demo,此Demo模仿ViewPage的滑动。

这里写图片描述

从demo中可以看出,红、蓝、黄分别装载在ViewGroup中,随着手势的滑动来显示相应的View。那么ViewGroup是什么呢?

这里写图片描述

从上图我们大概可以猜到ViewGroup是什么了,此刻我在想你们是否跟我想的一样,好我们来去看看官方的解释,看是否跟大家猜的一样:

“A ViewGroup is a special view that can contain other views (called children.) The view group is the base class for layouts and views containers. ”

翻译过来大概意思是:ViewGroup是一种特殊的视图可以包含其他视图(称为孩子)的视图组基类的布局和视图的容器。

那ViewGroup怎么实现呢?这个简单,按官方文档上面的例子,实践并总结出除了构造方法之外onMeasure()、onLayout(),这两方法是必须实现的,不然你的View就无法显示在屏幕上面。看以下示例:

 import android.content.Context;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.widget.LinearLayout;


/**
    * Created by win7 on 2017/7/4.
    */

public class BaseViewGroup extends ViewGroup {

private final String TAG = "BaseViewGroup";

private AView centerView;
private BView rigthView;
private CView leftView;
private VelocityTracker mVelocityTracker; //速率捕捉器

private final int SCREEN_LEFT = 1;  // 左屏
private final int SCREEN_CENTER = 2; //中屏
private final int SCREEN_RIGHT = 3;//右屏
private final int mUnits = 1000;//手势滑动速度
private final int mDuration = -1;
private int mTouchSlop; //滑动阀值
private int mMaximumVelocity; //最大移动速度
private int mActivePointerId;//活动触摸点
private int mCurrentScreen = 2; //当前所处屏
private int mScreenWidth; //屏幕宽度
private float mLastMotionX; //上一次移动点的位置

private boolean mPointInViews; //触摸点是否在指定范围内
private boolean mScrolled; //是否滑动

 public BaseViewGroup(Context context) {
    this(context, null);
 }

 public BaseViewGroup(Context context, AttributeSet attrs) {
    this(context, attrs, -1);
 }

 public BaseViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init(context);
 }

 private void init(Context context) {
    //获取屏幕宽度
    DisplayMetrics displayMetrics = new DisplayMetrics();
    WindowManager windowManager = (WindowManager) context
            .getSystemService(Context.WINDOW_SERVICE);
    windowManager.getDefaultDisplay().getMetrics(displayMetrics);
    mScreenWidth = displayMetrics.widthPixels;


    LinearLayout.LayoutParams aLayoutParams = new LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.WRAP_CONTENT);
    LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
            LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.MATCH_PARENT);

    //中屏
    centerView = new AView(context);
    this.addView(centerView, aLayoutParams);
    //右屏
    rigthView = new BView(context);
    this.addView(rigthView, layoutParams);
    //左屏
    leftView = new CView(context);
    this.addView(leftView, layoutParams);
    //获取标准的常量用来设置UI的超时、大小和距离
    ViewConfiguration configuration = ViewConfiguration.get(context);
    //获取用户认为滚动之前,触摸可以移动像素的距离。
    mTouchSlop = configuration.getScaledTouchSlop();
    //获取滑动的最大速度, 以像素/每秒来进行计算
    mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();

}


/**
 *要求所有子View测量自己并计算这一点的测量值
 *基于View的布局.
 */
 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //测量原始的大小
    int defaultWidth = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
    int defaultHeight = getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
    //测量子View的大小
    final int count = getChildCount();
    for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
    }

    //保存测量结果
    setMeasuredDimension(defaultWidth, defaultHeight);
}

/**
 *在布局中放置所有的子节点
 */ 
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int width = this.getWidth();
    int height = this.getHeight();
    //对子View进行排版
    centerView.layout(0, 0, width, height);
    rigthView.layout(0, 0, width, height);
    leftView.layout(0, 0, width, height);
    //改变left和right的坐标点位置,以达到左、中、右三屏效果
    rigthView.scrollTo(-width, 0);
    leftView.scrollTo(width, 0);

 }

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    return super.dispatchTouchEvent(ev);
}

/**
 *判断触摸点是否在子View上
 *@param ev
 *@return
 */
public boolean pointInViews(MotionEvent ev) {
    boolean rigthView = false;
    float x = ev.getX();
    float y = ev.getY();
    if (mCurrentScreen == SCREEN_CENTER) {
        return centerView.pointInViews(x, y);
    } else if (mCurrentScreen == SCREEN_LEFT) {
        return leftView.pointInViews(x, y);
    } else if (mCurrentScreen == SCREEN_RIGHT) {
        return this.rigthView.pointInViews(x, y);
    }
    return rigthView;
}

/**事件拦截*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    mPointInViews = pointInViews(ev);
    if (!mPointInViews) {
        return false;
    }
    final float x = ev.getX();
    final float y = ev.getY();
    // 防止事件冲突
    getParent().requestDisallowInterceptTouchEvent(true);
    boolean handle = super.onInterceptTouchEvent(ev);
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mLastMotionX = x;
            //获取手势触控点
            if (ev.getPointerCount() > 0) {
                mActivePointerId = ev.getPointerId(0);
            }
            break;
        case MotionEvent.ACTION_MOVE:
            //根据手势滑动距离来判断是否拦截此次手势事件
            if (Math.abs(x - mLastMotionX) >= mTouchSlop) {
                handle = true;
            }
            break;
        case MotionEvent.ACTION_UP:
            break;
    }

    return handle;
}


@Override
public boolean onTouchEvent(MotionEvent event) {

    if (!mPointInViews) {
        return false;
    }

    if (null == mVelocityTracker) {
        //初始化速率捕捉器
        mVelocityTracker = VelocityTracker.obtain();
    }
    if (null != mVelocityTracker) {
        //删除手势
        mVelocityTracker.addMovement(event);
    }

    switch (event.getAction() & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_DOWN:
            //记录手势按下的X坐标点
            mLastMotionX = event.getX();
            mScrolled = false;
            break;
        case MotionEvent.ACTION_MOVE:
            //是否有手势触控点
            final int activePointerIndex = event.getPointerCount() > mActivePointerId ? event.findPointerIndex(mActivePointerId) : -1;
            if (activePointerIndex == -1) {
                break;
            }
            //获取手势触控点的X坐标
            int x = (int) event.getX(activePointerIndex);
            //计算滑动过程
            int move = (int) mLastMotionX - x;
            if (mScrolled) {
                mLastMotionX = x;
                onTouchMove(move);
            } else if (Math.abs(x - mLastMotionX) >= mTouchSlop) {
                mScrolled = true;
                onTouchMove(move);
            }

            break;
        case MotionEvent.ACTION_POINTER_UP:
        case MotionEvent.ACTION_UP:
            if (mActivePointerId < 0) {
                break;
            }
            final int activeUpPointerIndex = event.getPointerCount() > mActivePointerId ? event.findPointerIndex(mActivePointerId) : -1;
            if (activeUpPointerIndex == -1) {
                break;
            }

            if (event.getPointerId(event.getActionIndex()) != mActivePointerId) {
                break;
            }
            //计算当前手势的速度
            mVelocityTracker.computeCurrentVelocity(mUnits, mMaximumVelocity);
            //获取当前手势X移动的速度
            float mVelocityX = mVelocityTracker.getXVelocity();
            mActivePointerId = -1;
            scrollToNextScreen(mVelocityX);
            break;
        case MotionEvent.ACTION_POINTER_DOWN:
            break;
        case MotionEvent.ACTION_CANCEL:
            releaseTracker();

    }

    return true;
}

/**
 *释放速率捕捉器
 */
private void releaseTracker() {
    if (mVelocityTracker != null) {
        mVelocityTracker.recycle();
        mVelocityTracker = null;
    }
}


/**
 *根据X坐标位移
 */
private void onTouchMove(int move) {
    if (mCurrentScreen == SCREEN_LEFT) {
        leftView.scrollBy(move, 0);
        centerView.scrollBy(move, 0);
    } else if (mCurrentScreen == SCREEN_CENTER) {
        centerView.scrollBy(move, 0);
        leftView.scrollBy(move, 0);
        rigthView.scrollBy(move, 0);
    } else if (mCurrentScreen == SCREEN_RIGHT) {
        rigthView.scrollBy(move, 0);
        centerView.scrollBy(move, 0);
    }
}


/**
 *根据手势的速度来判断滑动的方向
 *@param mVelocityX 当前手势X移动的速度
 */
private void scrollToNextScreen(float mVelocityX) {
    boolean isScrollToNextScreen = false;
    if (mVelocityX > mUnits) {
        isScrollToNextScreen = false;
    } else if (mVelocityX < -mUnits) {
        isScrollToNextScreen = true;
    } else {
        if (leftView.isHalfDisplaying()) {
            // left
            snapTo(SCREEN_LEFT, mScreenWidth);
        } else if (rigthView.isHalfDisplaying()) {
            // right
            snapTo(SCREEN_RIGHT, mScreenWidth);
        } else {
            snapTo(SCREEN_CENTER, mScreenWidth);
        }

        return;
    }
    int nextScreen;
    if (leftView.isScrolling() || leftView.isAllDisplaying()) {
        nextScreen = SCREEN_LEFT;
    } else if (rigthView.isAllDisplaying()) {
        nextScreen = SCREEN_RIGHT;
    } else {
        nextScreen = SCREEN_CENTER;
    }

    if (isScrollToNextScreen) {
        nextScreen++;
    } else {
        if (nextScreen != SCREEN_LEFT && mCurrentScreen != SCREEN_RIGHT) {
            nextScreen--;
        }
    }

    if (nextScreen > SCREEN_RIGHT) {
        snapTo(nextScreen, mScreenWidth);
    } else if (nextScreen == SCREEN_RIGHT) {
        snapTo(nextScreen, mScreenWidth);
    } else if (nextScreen <= SCREEN_RIGHT && nextScreen >= SCREEN_LEFT) {
        snapTo(nextScreen, mScreenWidth);
    }
}

 /**
  *滑动到某一屏
  *@param toCurrent
  *@param x
  */
private void snapTo(int toCurrent, int x) {

    if (SCREEN_LEFT == toCurrent) {

        leftView.snapTo(0, mDuration);
        centerView.snapTo(-x, mDuration);
        rigthView.snapTo(-x, 0);
        mCurrentScreen = 1;

    } else if (SCREEN_CENTER == toCurrent) {

        centerView.snapTo(0, mDuration);
        rigthView.snapTo(-x, mDuration);
        leftView.snapTo(x, mDuration);
        mCurrentScreen = SCREEN_CENTER;

    } else if (SCREEN_RIGHT == toCurrent) {

        rigthView.snapTo(0, mDuration);
        centerView.snapTo(x, mDuration);
        leftView.snapTo(x, mDuration);
        mCurrentScreen = SCREEN_RIGHT;

    }
}

}

onMeasure()、onLayout()这个两个重要的函数,我会在后面的ViewGroup、View之onMeasure、onLayout解刨中详细讲解。
onInterceptTouchEvent 我会在后面的View和ViewGroup事件分发机制中讲解

到此,对Viewgroup的讲解就暂告一段落,建议大家动手实践,附上Dmoe源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值