今天來看一下繼承自ViewGroup的自定義view;看看效果先:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0NXYFhGd192UvwVe0lmdhJ3ZvwFM38CXlZHbvN3cpR2Lc1TPB10QGtWUCpEMJ9CXsxWam9CXwADNvwVZ6l2c052bm9CXUJDT1wkNhVzLcRnbvZ2Lc1TP3NmZSNDW1x2RiZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39TMwcDM1cTN1ETNyITM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
有點像垂直方向的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 = ;
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 = ; 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 = ; i < count; i++) {
View childView = getChildAt(i);
if (childView.getVisibility() != View.GONE) {
childView.layout(l, i * mScreenHeight, r, (i + ) * 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 < && scrollY + dy < ) {
dy = -scrollY;//此時dy=0
}
// 已經到達底部
if (dy > && scrollY + dy > getHeight() - mScreenHeight) {
dy = getHeight() - mScreenHeight - scrollY;//此時dy=0
}
scrollBy(, 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 > ) {// 往上滑動
if (shouldScrollToNext(dScrollY)) {
mScroller.startScroll(, getScrollY(), , mScreenHeight - dScrollY);
} else {
mScroller.startScroll(, getScrollY(), , -dScrollY);
}
} else {// 往上滑
if (shouldScrollToPre(dScrollY)) {
mScroller.startScroll(, getScrollY(), , -mScreenHeight - dScrollY);
} else {
mScroller.startScroll(, getScrollY(), , -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(, 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 / || Math.abs(getVelocity()) > ;
}
/**
* 滑動到上一頁
*
* @param dScrollY
* @return
*/
private boolean shouldScrollToPre(int dScrollY) {
// TODO Auto-generated method stub
return -dScrollY > mScreenHeight / || Math.abs(getVelocity()) > ;
}
/**
* 擷取Y方向的加速度
* @return
*/
private double getVelocity() {
// TODO Auto-generated method stub
mVelocityTracker.computeCurrentVelocity();
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 + ) + "頁", Toast.LENGTH_SHORT).show();
}
下載下傳位址:http://download.csdn.net/my