自定义控件,较常用View、ViewGroup、Scroller三个类,其继承关系如下:

 

本示例自定义控件,实现一个Gallery效果,并添加了一个显示View个数和位置的bar条,效果图:

 

自定义控件,包含通过继承实现的自定义控件和自定义控件属性两部分,即控件和属性

1、自定义属性

自定义属性,分为定义属性、解析属性、设置属性三部分,具体步骤:

首先,在res/valus/attrs.xml属性资源文件中,定义控件属性

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="com.myapps.widget.Pager">
        <attr name="pageWidth" format="dimension" />
    </declare-styleable>
    
    <declare-styleable name="com.myapps.widget.PagerBar">
        <attr name="barColor" format="color" />
        <attr name="highlightColor" format="color" />
        <attr name="fadeDelay" format="integer" />
        <attr name="fadeDuration" format="integer" />
        <attr name="roundRectRadius" format="dimension" />
    </declare-styleable>
</resources>


然后,在自定义控件的代码中,解析自定义的属性,如在PagerBar.java:

// 自定义属性
		TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_PagerBar);
		int barBackColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_barColor, DEFAULT_BAR_BACKCOLOR);				// bar背景色
		int barForeColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_highlightColor, DEFAULT_BAR_FORECOLOR);	// bar前景色
		fadeDelay = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDelay, DEFAULT_FADE_DELAY);				// bar消失延迟时间
		fadeDuration = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDuration, DEFAULT_FADE_DURATION);	// bar消失动画时间
		ovalRadius = a.getDimension(R.styleable.com_myapps_widget_PagerBar_roundRectRadius, 2f);
		a.recycle();


最后,在布局文件中设置属性,如在main.xml

    <com.homer.mycontrol.PagerBar
        android:id="@+id/control"
        android:layout_width="fill_parent"
        android:layout_height="4dip"
        android:layout_margin="8dip"
        myapps:roundRectRadius="2dip" />	 <!-- 自定义圆角 -->

 

其中,在布局中间main.xml中,需要注意:

xmlns: myapps ="http://schemas.android.com/apk/res/com.homer.mycontrol"

定义属性时,在declare-styleable的name中,需要包含com. myapps .widget.PagerBar,表示自定义的控件PageBar是widget子类,myapps是xmlns解析标记

解析属性时,在TypedArray中,需要包含R.styleable.com_ myapps _widget_PagerBar,横线替换了圆点.

定义属性时,在com.homer.mycontrol.PagerBar中,需要包含 myapps :roundRectRadius="2dip",加上myapps解析标记

 

2、自定义控件PagerBar

自定义PagerBar,在图片下方用来显示图片滑到了第几页,即上面效果图(图2、图3)中的下部银白色细条,具体实现:

	public PagerBar(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		// 自定义属性
		TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_PagerBar);
		int barBackColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_barColor, DEFAULT_BAR_BACKCOLOR);				// bar背景色
		int barForeColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_highlightColor, DEFAULT_BAR_FORECOLOR);	// bar前景色
		fadeDelay = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDelay, DEFAULT_FADE_DELAY);				// bar消失延迟时间
		fadeDuration = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDuration, DEFAULT_FADE_DURATION);	// bar消失动画时间
		ovalRadius = a.getDimension(R.styleable.com_myapps_widget_PagerBar_roundRectRadius, 2f);
		a.recycle();

		barBackPaint = new Paint();
		barBackPaint.setColor(barBackColor);

		barForePaint = new Paint();
		barForePaint.setColor(barForeColor);

		fadeOutAnimation = new AlphaAnimation(1f, 0f);
		fadeOutAnimation.setDuration(fadeDuration);
		fadeOutAnimation.setRepeatCount(0);
		fadeOutAnimation.setInterpolator(new LinearInterpolator());
		fadeOutAnimation.setFillEnabled(true);
		fadeOutAnimation.setFillAfter(true);
	}

	public int getNumPages() {
		return numPages;
	}

	public void setNumPages(int numPages) {
		if (numPages <= 0) {
			throw new IllegalArgumentException("numPages must be positive");
		}
		this.numPages = numPages;
		invalidate();		// 重绘View
		fadeOut();			// 设置bar消失效果
	}

	/** bar消失动画 */
	private void fadeOut() {
		if (fadeDuration > 0) {
			clearAnimation();
			fadeOutAnimation.setStartTime(AnimationUtils.currentAnimationTimeMillis() + fadeDelay);	//延迟fadeDelay后动画开始
			setAnimation(fadeOutAnimation);
		}
	}

	/**  @return  0 to numPages-1 */
	public int getCurrentPage() {
		return currentPage;
	}

	/** @param currentPage  0 to numPages-1  */
	public void setCurrentPage(int currentPage) {
		if (currentPage < 0 || currentPage >= numPages) {
			throw new IllegalArgumentException("currentPage parameter out of bounds");
		}
		if (this.currentPage != currentPage) {
			this.currentPage = currentPage;
			this.position = currentPage * getPageWidth();	// bar前景色滑动条的起始位置(像素值)
			invalidate();
			fadeOut();
		}
	}

	/** 获取View的宽度,即bar的宽度 */
	public int getPageWidth() {
		return getWidth() / numPages;	// getWidth()是PagerBar的宽度(减去了margin左右距离后)
	}

	/**  @param position     can be -pageWidth to pageWidth*(numPages+1)  */
	public void setPosition(int position) {
		if (this.position != position) {
			this.position = position;
			invalidate();
			fadeOut();
		}
	}

	@Override
	protected void onDraw(Canvas canvas) {
		canvas.drawRoundRect(new RectF(0, 0, getWidth(), getHeight()), ovalRadius, ovalRadius, barBackPaint);	// 绘制bar背景
		canvas.drawRoundRect(new RectF(position, 0, position + (getWidth() / numPages), getHeight()), ovalRadius, ovalRadius, barForePaint);	// 绘制bar前景
	}
}

 

3、自定义控件Pager

自定义控件Pager,继承自ViewGroup,用来显示图片的,类似于Gallery,实现主要部分包含:

A、自定义属性解析

B、Pager容器控件Scroller滑动页设置与控制

C、容器状态保存(onSaveInstanceState)

D、容器事件监听接口

 

详细实现如下:

A、自定义属性解析

	public Pager(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);

		// 自定义属性
		TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_Pager);
		pageWidthSpec = a.getDimensionPixelSize(R.styleable.com_myapps_widget_Pager_pageWidth, SPEC_UNDEFINED);
		a.recycle();
	}

 

B、Pager容器控件Scroller滑动页设置与控制

 

	public void setCurrentPage(int currentPage) {
		mCurrentPage = Math.max(0, Math.min(currentPage, getChildCount()));		// 非常好
		scrollTo(getScrollXForPage(mCurrentPage), 0);
		invalidate();	// 重绘View
	}

	int getCurrentPage() {
		return mCurrentPage;
	}

	public void setPageWidth(int pageWidth) {
		this.pageWidthSpec = pageWidth;
	}

	public int getPageWidth() {
		return pageWidth;
	}

	/** 获取whichPage的Pager起始x位置,whichPage从0开始计 */
	private int getScrollXForPage(int whichPage) {
		return (whichPage * pageWidth) - pageWidthPadding();
	}

	/** 返回View的 paddingwidth 一半(1/2)*/
	int pageWidthPadding() {
		return ((getMeasuredWidth() - pageWidth) / 2);
	}

	@Override
	public void computeScroll() {		// update  mScrollX and mScrollY  of View
		if (mScroller.computeScrollOffset()) {
			scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
			postInvalidate();			// invalidate the View from a non-UI thread
		} else if (mNextPage != INVALID_SCREEN) {
			mCurrentPage = mNextPage;
			mNextPage = INVALID_SCREEN;
			clearChildrenCache();
		}
	}

	@Override
	protected void dispatchDraw(Canvas canvas) {	// draw the child views
		
		final long drawingTime = getDrawingTime();	// 绘制childView
		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			drawChild(canvas, getChildAt(i), drawingTime);
		}

		for (OnScrollListener mListener : mListeners) {	// 自定义接口
			int adjustedScrollX = getScrollX() + pageWidthPadding();
			mListener.onScroll(adjustedScrollX);
			if (adjustedScrollX % pageWidth == 0) {	// scroll finished
				mListener.onViewScrollFinished(adjustedScrollX / pageWidth);
			}
		}
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		pageWidth = (pageWidthSpec == SPEC_UNDEFINED) ? getMeasuredWidth() : pageWidthSpec;
		pageWidth = Math.min(pageWidth, getMeasuredWidth());

		final int count = getChildCount();
		for (int i = 0; i < count; i++) {
			widthMeasureSpec = MeasureSpec.makeMeasureSpec(pageWidth, MeasureSpec.EXACTLY);
			getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
		}

		if (mFirstLayout) {	// 第一次显示Pager时,page的位置
			scrollTo(getScrollXForPage(mCurrentPage), mScroller.getCurrY());
			mFirstLayout = false;
		}
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
		int childLeft = 0;

		final int count = getChildCount();	// 绘制childView
		for (int i = 0; i < count; i++) {
			final View child = getChildAt(i);
			if (child.getVisibility() != View.GONE) {
				final int childWidth = child.getMeasuredWidth();
				child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
				childLeft += childWidth;
			}
		}
	}
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		final int action = ev.getAction();
		if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {	// 正在滑动中
			return true;
		}

		final float x = ev.getX();
		final float y = ev.getY();

		switch (action) {
		case MotionEvent.ACTION_MOVE:
			if (mTouchState == TOUCH_STATE_REST) {
				checkStartScroll(x, y);
			}
			break;

		case MotionEvent.ACTION_DOWN:
			mLastMotionX = x;
			mLastMotionY = y;
			mAllowLongPress = true;

			mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;	// scroll 完成后,重置状态
			break;

		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:
			clearChildrenCache();
			mTouchState = TOUCH_STATE_REST;
			break;
		}

		return mTouchState != TOUCH_STATE_REST;
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (mVelocityTracker == null) {
			mVelocityTracker = VelocityTracker.obtain();
		}
		mVelocityTracker.addMovement(ev);

		final int action = ev.getAction();
		final float x = ev.getX();
		final float y = ev.getY();

		switch (action) {
		case MotionEvent.ACTION_DOWN:
			if (!mScroller.isFinished()) {
				mScroller.abortAnimation();
			}
			mLastMotionX = x;
			break;
			
		case MotionEvent.ACTION_MOVE:
			if (mTouchState == TOUCH_STATE_REST) {
				checkStartScroll(x, y);
			} else if (mTouchState == TOUCH_STATE_SCROLLING) {	// scrolling 状态时,重绘view
				int deltaX = (int) (mLastMotionX - x);
				mLastMotionX = x;

				if (getScrollX() < 0 || getScrollX() > getChildAt(getChildCount() - 1).getLeft()) {
					deltaX /= 2;
				}
				scrollBy(deltaX, 0);
			}
			break;
			
		case MotionEvent.ACTION_UP:
			if (mTouchState == TOUCH_STATE_SCROLLING) {
				final VelocityTracker velocityTracker = mVelocityTracker;
				velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
				int velocityX = (int) velocityTracker.getXVelocity();

				if (velocityX > SNAP_VELOCITY && mCurrentPage > 0) {
					snapToPage(mCurrentPage - 1);
				} else if (velocityX < -SNAP_VELOCITY && mCurrentPage < getChildCount() - 1) {
					snapToPage(mCurrentPage + 1);
				} else {
					snapToDestination();
				}

				if (mVelocityTracker != null) {
					mVelocityTracker.recycle();
					mVelocityTracker = null;
				}
			}
			mTouchState = TOUCH_STATE_REST;
			break;
		case MotionEvent.ACTION_CANCEL:
			mTouchState = TOUCH_STATE_REST;
		}

		return true;
	}

	/** 检查scroll状态,并设置绘制scroll缓存 */
	private void checkStartScroll(float x, float y) {
		final int xDiff = (int) Math.abs(x - mLastMotionX);
		final int yDiff = (int) Math.abs(y - mLastMotionY);

		boolean xMoved = xDiff > mTouchSlop;
		boolean yMoved = yDiff > mTouchSlop;

		if (xMoved || yMoved) {
			if (xMoved) {
				mTouchState = TOUCH_STATE_SCROLLING;		// 设置为scrolling 状态
				enableChildrenCache();
			}
			if (mAllowLongPress) {
				mAllowLongPress = false;
				final View currentScreen = getChildAt(mCurrentPage);
				currentScreen.cancelLongPress();	// Cancels a pending long press
			}
		}
	}

	void enableChildrenCache() {
		setChildrenDrawingCacheEnabled(true);		// Enables or disables the drawing cache for each child of this viewGroup
		setChildrenDrawnWithCacheEnabled(true);	// Tells the ViewGroup to draw its children using their drawing cache
	}

	void clearChildrenCache() {
		setChildrenDrawnWithCacheEnabled(false);
	}

	private void snapToDestination() {
		final int startX = getScrollXForPage(mCurrentPage);
		int whichPage = mCurrentPage;
		if (getScrollX() < startX - getWidth() / 8) {
			whichPage = Math.max(0, whichPage - 1);
		} else if (getScrollX() > startX + getWidth() / 8) {
			whichPage = Math.min(getChildCount() - 1, whichPage + 1);
		}

		snapToPage(whichPage);
	}

	void snapToPage(int whichPage) {
		enableChildrenCache();

		boolean changingPages = whichPage != mCurrentPage;

		mNextPage = whichPage;

		View focusedChild = getFocusedChild();
		if (focusedChild != null && changingPages && focusedChild == getChildAt(mCurrentPage)) {
			focusedChild.clearFocus();
		}

		final int newX = getScrollXForPage(whichPage);
		final int delta = newX - getScrollX();
		mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);
		invalidate();
	}

	/** 向左滑动 */
	public void scrollLeft() {
		if (mNextPage == INVALID_SCREEN && mCurrentPage > 0 && mScroller.isFinished()) {
			snapToPage(mCurrentPage - 1);
		}
	}

	/** 向右滑动 */
	public void scrollRight() {
		if (mNextPage == INVALID_SCREEN && mCurrentPage < getChildCount() - 1 && mScroller.isFinished()) {
			snapToPage(mCurrentPage + 1);
		}
	}

 

C、容器状态保存(onSaveInstanceState)

	/** 保存状态 */
	public static class SavedState extends BaseSavedState {
		int currentScreen = -1;

		SavedState(Parcelable superState) {
			super(superState);
		}

		private SavedState(Parcel in) {
			super(in);
			currentScreen = in.readInt();
		}

		public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
			public SavedState createFromParcel(Parcel in) {	// get data written by Parcelable.writeToParcel()	
				return new SavedState(in);
			}

			public SavedState[] newArray(int size) {			// create  array of the Parcelable 
				return new SavedState[size];
			}
		};
		
		@Override
		public void writeToParcel(Parcel out, int flags) {		// set data to parcel
			super.writeToParcel(out, flags);
			out.writeInt(currentScreen);
		}

	}

	@Override
	protected Parcelable onSaveInstanceState() {		// 保存状态
		final SavedState state = new SavedState(super.onSaveInstanceState());
		state.currentScreen = mCurrentPage;	 	// save InstanceState
		return state;
	}

	@Override
	protected void onRestoreInstanceState(Parcelable state) {	// 恢复状态
		SavedState savedState = (SavedState) state;
		super.onRestoreInstanceState(savedState.getSuperState());	// get InstanceState
		if (savedState.currentScreen != INVALID_SCREEN) {
			mCurrentPage = savedState.currentScreen;	
		}
	}

 

D、容器事件监听接口

	public void addOnScrollListener(OnScrollListener listener) {
		mListeners.add(listener);
	}

	public void removeOnScrollListener(OnScrollListener listener) {
		mListeners.remove(listener);
	}

	/** 自定义接口 */
	public static interface OnScrollListener {
		void onScroll(int scrollX);
		void onViewScrollFinished(int currentPage);
	}

 

代码下载

 

参考推荐:

Android中自定义属性的使用

Android中自定义属性的格式详解

Scroller (Android)

Scroller (cnblog)

Android Parcelable

Android左右滑动加载分页

 

原文: Android滑动效果高级篇(八)——自定义控件