今天来看一下继承自ViewGroup的自定义view;看看效果先:
有点像垂直方向的viewpager吧,下面来一步一步实现它吧。
step1声明需要的一些成员变量
// 屏幕高度
private int mScreenHeight;
private int mScrollStart;
private int mScrollEnd;
// 移动时的Y距离
private int mLastY;
// 滚动辅助类
private Scroller mScroller;
private boolean isScrolling;
// 加速度检测
private VelocityTracker mVelocityTracker;
private int currentPage = 0;
private onPageChangeListener mOnPageChangeListener;
private static final String TAG = "VerticalLinearLayout";
其中VelocityTracker 主要用跟踪触摸屏事件(flinging事件和其他gestures手势事件)的速率。用addMovement( MotionEvent)函数将Motion event加入到VelocityTracker类实例中.你可以使用getXVelocity() 或getXVelocity()获得横向和竖向的速率到速率时,但是使用它们之前请先调用computeCurrentVelocity(int) 来初始化速率的单位 。
onPageChangeListener 是自定义的回调接口
/**
* 设置回调接口
*/
public void SetOnPageChangeListener(onPageChangeListener listener) {
this.mOnPageChangeListener = listener;
}
public interface onPageChangeListener {
void onPageChange(int currentPage);
}
step2 在构造方法中初始化成员变量
// 获取屏幕的高度
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
wm.getDefaultDisplay().getMetrics(dm);
mScreenHeight = dm.heightPixels;
Log.e(TAG, "mScreenHeight= " + mScreenHeight);
mScroller = new Scroller(context);
step3在onMeasure()方法中计算每个子view的尺寸
int count = getChildCount();
for (int i = 0; i < count; i++) {
Log.e(TAG, "count= " + count);
View childView = getChildAt(i);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
}
step4在onLayout()中确定子view的位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// TODO Auto-generated method stub
if (changed) {
int count = getChildCount();
// 设置主布局的高度
MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
mlp.height = mScreenHeight * count;
setLayoutParams(mlp);
// 设置每个子布局的位置
for (int i = 0; i < count; i++) {
View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
childView.layout(l, i * mScreenHeight, r, (i + 1) * mScreenHeight);
}
}
}
}
onLayout的几个参数说明: 1)参数changed表示view有新的尺寸或位置; 2)参数l表示相对于父view的Left位置; 3)参数t表示相对于父view的Top位置; 4)参数r表示相对于父view的Right位置; 5)参数b表示相对于父view的Bottom位置。.
MarginLayoutParams是继承自ViewGroup.LayoutParams
子类有 FrameLayout.LayoutParams,LinearLayout.LayoutParams, RelativeLayout.LayoutParams
step5 处理触摸事件
// 如果正在滑动,不做处理,调用父类的触摸事件
Log.e(TAG, "isScrolling=" + isScrolling);
if (isScrolling) {
return super.onTouchEvent(event);
}
initVelocityTracker(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Return the scrolled top position of this view
mScrollStart = getScrollY();
mLastY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
if (!mScroller.isFinished()) {
// Stops the animation.
mScroller.abortAnimation();
}
int dy = mLastY - (int) event.getY();
int scrollY = getScrollY();
// 已经到达顶端
if (dy < 0 && scrollY + dy < 0) {
dy = -scrollY;//此时dy=0
}
// 已经到达底部
if (dy > 0 && scrollY + dy > getHeight() - mScreenHeight) {
dy = getHeight() - mScreenHeight - scrollY;//此时dy=0
}
scrollBy(0, dy);
mLastY = (int) event.getY();
break;
case MotionEvent.ACTION_UP:
mScrollEnd = getScrollY();
Log.e(TAG, "mScrollStart=" + mScrollStart);
Log.e(TAG, "mScrollEnd=" + mScrollEnd);
int dScrollY = mScrollEnd - mScrollStart;
if (dScrollY > 0) {// 往上滑动
if (shouldScrollToNext(dScrollY)) {
mScroller.startScroll(0, getScrollY(), 0, mScreenHeight - dScrollY);
} else {
mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
}
} else {// 往上滑
if (shouldScrollToPre(dScrollY)) {
mScroller.startScroll(0, getScrollY(), 0, -mScreenHeight - dScrollY);
} else {
mScroller.startScroll(0, getScrollY(), 0, -dScrollY);
}
}
isScrolling = true;
postInvalidate();
//回收资源
recycleVelocity();
break;
}
return true;
step6需要重写computeScroll()方法
为了易于控制滑屏控制,Android框架提供了 computeScroll()方法去控制这个流程。在绘制View时,会在draw()过程调用该 方法。因此, 再配合使用Scroller实例,我们就可以获得当前应该的偏移坐,手动使View/ViewGroup偏移至该处。
@Override
public void computeScroll() {
// TODO Auto-generated method stub
super.computeScroll();
// If it returns true, the animation is not yet finished.\
Log.e(TAG, "mScroller.computeScrollOffset()=" + mScroller.computeScrollOffset());
if (mScroller.computeScrollOffset()) {// 滑动结束的时候调用scrollTo()
scrollTo(0, mScroller.getCurrY());
postInvalidate();
} else {
int position = getScrollY() / mScreenHeight;
Log.e(TAG, "position=" + position + ",currentPage=" + currentPage);
if (position != currentPage) {
if (mOnPageChangeListener != null) {
currentPage = position;
mOnPageChangeListener.onPageChange(currentPage);
}
}
isScrolling = false;
}
}
一些小方法:
/**
* 释放资源
*/
private void recycleVelocity() {
// TODO Auto-generated method stub
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
/**
* 初始化加速度追踪器
*
* @param event
*/
private void initVelocityTracker(MotionEvent event) {
// TODO Auto-generated method stub
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
}
/**
* 滑动到下一页
*
* @param dScrollY
* @return
*/
private boolean shouldScrollToNext(int dScrollY) {
// TODO Auto-generated method stub
return dScrollY > mScreenHeight / 2 || Math.abs(getVelocity()) > 600;
}
/**
* 滑动到上一页
*
* @param dScrollY
* @return
*/
private boolean shouldScrollToPre(int dScrollY) {
// TODO Auto-generated method stub
return -dScrollY > mScreenHeight / 2 || Math.abs(getVelocity()) > 600;
}
/**
* 获取Y方向的加速度
* @return
*/
private double getVelocity() {
// TODO Auto-generated method stub
mVelocityTracker.computeCurrentVelocity(1000);
return (int) mVelocityTracker.getYVelocity();
}
至此,基本工作就完成了;
看看MainActivity
mMianLayout = (VerticalLinearLayout) findViewById(R.id.id_main_ly);
mMianLayout.SetOnPageChangeListener(this);
@Override
public void onPageChange(int currentPage) {
// TODO Auto-generated method stub
Toast.makeText(MainActivity.this, "第" + (currentPage + 1) + "页", Toast.LENGTH_SHORT).show();
}