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源码