自訂控制項,較常用View、ViewGroup、Scroller三個類,其繼承關係如下:
本樣本自訂控制項,實現一個Gallery效果,並添加了一個顯示View個數和位置的bar條,:
自訂控制項,包含通過繼承實現的自訂控制項和自訂控制項屬性兩部分,即控制項和屬性
1、自訂屬性
自訂屬性,分為定義屬性、解析屬性、設定屬性三部分,具體步驟:
首先,在res/valus/attrs.xml屬性資源檔中,定義控制項屬性
<?xml version="1.0" encoding="utf-8"?><br /><resources><br /> <declare-styleable name="com.myapps.widget.Pager"><br /> <attr name="pageWidth" format="dimension" /><br /> </declare-styleable></p><p> <declare-styleable name="com.myapps.widget.PagerBar"><br /> <attr name="barColor" format="color" /><br /> <attr name="highlightColor" format="color" /><br /> <attr name="fadeDelay" format="integer" /><br /> <attr name="fadeDuration" format="integer" /><br /> <attr name="roundRectRadius" format="dimension" /><br /> </declare-styleable><br /></resources>
然後,在自訂控制項的代碼中,解析自訂的屬性,如在PagerBar.java:
// 自訂屬性<br />TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_PagerBar);<br />int barBackColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_barColor, DEFAULT_BAR_BACKCOLOR);// bar背景色<br />int barForeColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_highlightColor, DEFAULT_BAR_FORECOLOR);// bar前景色彩<br />fadeDelay = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDelay, DEFAULT_FADE_DELAY);// bar消失延遲時間<br />fadeDuration = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDuration, DEFAULT_FADE_DURATION);// bar消失動畫時間<br />ovalRadius = a.getDimension(R.styleable.com_myapps_widget_PagerBar_roundRectRadius, 2f);<br />a.recycle();
最後,在布局檔案中設定屬性,如在main.xml
<com.homer.mycontrol.PagerBar<br /> android:id="@+id/control"<br /> android:layout_width="fill_parent"<br /> android:layout_height="4dip"<br /> android:layout_margin="8dip"<br /> 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) {<br />super(context, attrs, defStyle);</p><p>// 自訂屬性<br />TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_PagerBar);<br />int barBackColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_barColor, DEFAULT_BAR_BACKCOLOR);// bar背景色<br />int barForeColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_highlightColor, DEFAULT_BAR_FORECOLOR);// bar前景色彩<br />fadeDelay = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDelay, DEFAULT_FADE_DELAY);// bar消失延遲時間<br />fadeDuration = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDuration, DEFAULT_FADE_DURATION);// bar消失動畫時間<br />ovalRadius = a.getDimension(R.styleable.com_myapps_widget_PagerBar_roundRectRadius, 2f);<br />a.recycle();</p><p>barBackPaint = new Paint();<br />barBackPaint.setColor(barBackColor);</p><p>barForePaint = new Paint();<br />barForePaint.setColor(barForeColor);</p><p>fadeOutAnimation = new AlphaAnimation(1f, 0f);<br />fadeOutAnimation.setDuration(fadeDuration);<br />fadeOutAnimation.setRepeatCount(0);<br />fadeOutAnimation.setInterpolator(new LinearInterpolator());<br />fadeOutAnimation.setFillEnabled(true);<br />fadeOutAnimation.setFillAfter(true);<br />}</p><p>public int getNumPages() {<br />return numPages;<br />}</p><p>public void setNumPages(int numPages) {<br />if (numPages <= 0) {<br />throw new IllegalArgumentException("numPages must be positive");<br />}<br />this.numPages = numPages;<br />invalidate();// 重繪View<br />fadeOut();// 設定bar消失效果<br />}</p><p>/** bar消失動畫 */<br />private void fadeOut() {<br />if (fadeDuration > 0) {<br />clearAnimation();<br />fadeOutAnimation.setStartTime(AnimationUtils.currentAnimationTimeMillis() + fadeDelay);//延遲fadeDelay後動畫開始<br />setAnimation(fadeOutAnimation);<br />}<br />}</p><p>/** @return 0 to numPages-1 */<br />public int getCurrentPage() {<br />return currentPage;<br />}</p><p>/** @param currentPage 0 to numPages-1 */<br />public void setCurrentPage(int currentPage) {<br />if (currentPage < 0 || currentPage >= numPages) {<br />throw new IllegalArgumentException("currentPage parameter out of bounds");<br />}<br />if (this.currentPage != currentPage) {<br />this.currentPage = currentPage;<br />this.position = currentPage * getPageWidth();// bar前景色彩滑動條的起始位置(像素值)<br />invalidate();<br />fadeOut();<br />}<br />}</p><p>/** 擷取View的寬度,即bar的寬度 */<br />public int getPageWidth() {<br />return getWidth() / numPages;// getWidth()是PagerBar的寬度(減去了margin左右距離後)<br />}</p><p>/** @param position can be -pageWidth to pageWidth*(numPages+1) */<br />public void setPosition(int position) {<br />if (this.position != position) {<br />this.position = position;<br />invalidate();<br />fadeOut();<br />}<br />}</p><p>@Override<br />protected void onDraw(Canvas canvas) {<br />canvas.drawRoundRect(new RectF(0, 0, getWidth(), getHeight()), ovalRadius, ovalRadius, barBackPaint);// 繪製bar背景<br />canvas.drawRoundRect(new RectF(position, 0, position + (getWidth() / numPages), getHeight()), ovalRadius, ovalRadius, barForePaint);// 繪製bar前景<br />}<br />}
3、自訂控制項Pager
自訂控制項Pager,繼承自ViewGroup,用來顯示圖片的,類似於Gallery,實現主要部分包含:
A、自訂屬性解析
B、Pager容器控制項Scroller滑動頁設定與控制
C、容器狀態儲存(onSaveInstanceState)
D、容器事件監聽介面
詳細實現如下:
A、自訂屬性解析
public Pager(Context context, AttributeSet attrs, int defStyle) {<br />super(context, attrs, defStyle);</p><p>// 自訂屬性<br />TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_Pager);<br />pageWidthSpec = a.getDimensionPixelSize(R.styleable.com_myapps_widget_Pager_pageWidth, SPEC_UNDEFINED);<br />a.recycle();<br />}
B、Pager容器控制項Scroller滑動頁設定與控制
public void setCurrentPage(int currentPage) {<br />mCurrentPage = Math.max(0, Math.min(currentPage, getChildCount()));// 非常好<br />scrollTo(getScrollXForPage(mCurrentPage), 0);<br />invalidate();// 重繪View<br />}</p><p>int getCurrentPage() {<br />return mCurrentPage;<br />}</p><p>public void setPageWidth(int pageWidth) {<br />this.pageWidthSpec = pageWidth;<br />}</p><p>public int getPageWidth() {<br />return pageWidth;<br />}</p><p>/** 擷取whichPage的Pager起始x位置,whichPage從0開始計 */<br />private int getScrollXForPage(int whichPage) {<br />return (whichPage * pageWidth) - pageWidthPadding();<br />}</p><p>/** 返回View的 paddingwidth 一半(1/2)*/<br />int pageWidthPadding() {<br />return ((getMeasuredWidth() - pageWidth) / 2);<br />}</p><p>@Override<br />public void computeScroll() {// update mScrollX and mScrollY of View<br />if (mScroller.computeScrollOffset()) {<br />scrollTo(mScroller.getCurrX(), mScroller.getCurrY());<br />postInvalidate();// invalidate the View from a non-UI thread<br />} else if (mNextPage != INVALID_SCREEN) {<br />mCurrentPage = mNextPage;<br />mNextPage = INVALID_SCREEN;<br />clearChildrenCache();<br />}<br />}</p><p>@Override<br />protected void dispatchDraw(Canvas canvas) {// draw the child views</p><p>final long drawingTime = getDrawingTime();// 繪製childView<br />final int count = getChildCount();<br />for (int i = 0; i < count; i++) {<br />drawChild(canvas, getChildAt(i), drawingTime);<br />}</p><p>for (OnScrollListener mListener : mListeners) {// 自訂介面<br />int adjustedScrollX = getScrollX() + pageWidthPadding();<br />mListener.onScroll(adjustedScrollX);<br />if (adjustedScrollX % pageWidth == 0) {// scroll finished<br />mListener.onViewScrollFinished(adjustedScrollX / pageWidth);<br />}<br />}<br />}</p><p>@Override<br />protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {<br />super.onMeasure(widthMeasureSpec, heightMeasureSpec);</p><p>pageWidth = (pageWidthSpec == SPEC_UNDEFINED) ? getMeasuredWidth() : pageWidthSpec;<br />pageWidth = Math.min(pageWidth, getMeasuredWidth());</p><p>final int count = getChildCount();<br />for (int i = 0; i < count; i++) {<br />widthMeasureSpec = MeasureSpec.makeMeasureSpec(pageWidth, MeasureSpec.EXACTLY);<br />getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);<br />}</p><p>if (mFirstLayout) {// 第一次顯示Pager時,page的位置<br />scrollTo(getScrollXForPage(mCurrentPage), mScroller.getCurrY());<br />mFirstLayout = false;<br />}<br />}</p><p>@Override<br />protected void onLayout(boolean changed, int left, int top, int right, int bottom) {<br />int childLeft = 0;</p><p>final int count = getChildCount();// 繪製childView<br />for (int i = 0; i < count; i++) {<br />final View child = getChildAt(i);<br />if (child.getVisibility() != View.GONE) {<br />final int childWidth = child.getMeasuredWidth();<br />child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());<br />childLeft += childWidth;<br />}<br />}<br />}
@Override<br />public boolean onInterceptTouchEvent(MotionEvent ev) {<br />final int action = ev.getAction();<br />if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) {// 正在滑動中<br />return true;<br />}</p><p>final float x = ev.getX();<br />final float y = ev.getY();</p><p>switch (action) {<br />case MotionEvent.ACTION_MOVE:<br />if (mTouchState == TOUCH_STATE_REST) {<br />checkStartScroll(x, y);<br />}<br />break;</p><p>case MotionEvent.ACTION_DOWN:<br />mLastMotionX = x;<br />mLastMotionY = y;<br />mAllowLongPress = true;</p><p>mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;// scroll 完成後,重設狀態<br />break;</p><p>case MotionEvent.ACTION_CANCEL:<br />case MotionEvent.ACTION_UP:<br />clearChildrenCache();<br />mTouchState = TOUCH_STATE_REST;<br />break;<br />}</p><p>return mTouchState != TOUCH_STATE_REST;<br />}</p><p>@Override<br />public boolean onTouchEvent(MotionEvent ev) {<br />if (mVelocityTracker == null) {<br />mVelocityTracker = VelocityTracker.obtain();<br />}<br />mVelocityTracker.addMovement(ev);</p><p>final int action = ev.getAction();<br />final float x = ev.getX();<br />final float y = ev.getY();</p><p>switch (action) {<br />case MotionEvent.ACTION_DOWN:<br />if (!mScroller.isFinished()) {<br />mScroller.abortAnimation();<br />}<br />mLastMotionX = x;<br />break;</p><p>case MotionEvent.ACTION_MOVE:<br />if (mTouchState == TOUCH_STATE_REST) {<br />checkStartScroll(x, y);<br />} else if (mTouchState == TOUCH_STATE_SCROLLING) {// scrolling 狀態時,重繪view<br />int deltaX = (int) (mLastMotionX - x);<br />mLastMotionX = x;</p><p>if (getScrollX() < 0 || getScrollX() > getChildAt(getChildCount() - 1).getLeft()) {<br />deltaX /= 2;<br />}<br />scrollBy(deltaX, 0);<br />}<br />break;</p><p>case MotionEvent.ACTION_UP:<br />if (mTouchState == TOUCH_STATE_SCROLLING) {<br />final VelocityTracker velocityTracker = mVelocityTracker;<br />velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);<br />int velocityX = (int) velocityTracker.getXVelocity();</p><p>if (velocityX > SNAP_VELOCITY && mCurrentPage > 0) {<br />snapToPage(mCurrentPage - 1);<br />} else if (velocityX < -SNAP_VELOCITY && mCurrentPage < getChildCount() - 1) {<br />snapToPage(mCurrentPage + 1);<br />} else {<br />snapToDestination();<br />}</p><p>if (mVelocityTracker != null) {<br />mVelocityTracker.recycle();<br />mVelocityTracker = null;<br />}<br />}<br />mTouchState = TOUCH_STATE_REST;<br />break;<br />case MotionEvent.ACTION_CANCEL:<br />mTouchState = TOUCH_STATE_REST;<br />}</p><p>return true;<br />}</p><p>/** 檢查scroll狀態,並設定繪製scroll緩衝 */<br />private void checkStartScroll(float x, float y) {<br />final int xDiff = (int) Math.abs(x - mLastMotionX);<br />final int yDiff = (int) Math.abs(y - mLastMotionY);</p><p>boolean xMoved = xDiff > mTouchSlop;<br />boolean yMoved = yDiff > mTouchSlop;</p><p>if (xMoved || yMoved) {<br />if (xMoved) {<br />mTouchState = TOUCH_STATE_SCROLLING;// 設定為scrolling 狀態<br />enableChildrenCache();<br />}<br />if (mAllowLongPress) {<br />mAllowLongPress = false;<br />final View currentScreen = getChildAt(mCurrentPage);<br />currentScreen.cancelLongPress();// Cancels a pending long press<br />}<br />}<br />}</p><p>void enableChildrenCache() {<br />setChildrenDrawingCacheEnabled(true);// Enables or disables the drawing cache for each child of this viewGroup<br />setChildrenDrawnWithCacheEnabled(true);// Tells the ViewGroup to draw its children using their drawing cache<br />}</p><p>void clearChildrenCache() {<br />setChildrenDrawnWithCacheEnabled(false);<br />}</p><p>private void snapToDestination() {<br />final int startX = getScrollXForPage(mCurrentPage);<br />int whichPage = mCurrentPage;<br />if (getScrollX() < startX - getWidth() / 8) {<br />whichPage = Math.max(0, whichPage - 1);<br />} else if (getScrollX() > startX + getWidth() / 8) {<br />whichPage = Math.min(getChildCount() - 1, whichPage + 1);<br />}</p><p>snapToPage(whichPage);<br />}</p><p>void snapToPage(int whichPage) {<br />enableChildrenCache();</p><p>boolean changingPages = whichPage != mCurrentPage;</p><p>mNextPage = whichPage;</p><p>View focusedChild = getFocusedChild();<br />if (focusedChild != null && changingPages && focusedChild == getChildAt(mCurrentPage)) {<br />focusedChild.clearFocus();<br />}</p><p>final int newX = getScrollXForPage(whichPage);<br />final int delta = newX - getScrollX();<br />mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);<br />invalidate();<br />}</p><p>/** 向左滑動 */<br />public void scrollLeft() {<br />if (mNextPage == INVALID_SCREEN && mCurrentPage > 0 && mScroller.isFinished()) {<br />snapToPage(mCurrentPage - 1);<br />}<br />}</p><p>/** 向右滑動 */<br />public void scrollRight() {<br />if (mNextPage == INVALID_SCREEN && mCurrentPage < getChildCount() - 1 && mScroller.isFinished()) {<br />snapToPage(mCurrentPage + 1);<br />}<br />}
C、容器狀態儲存(onSaveInstanceState)
/** 儲存狀態 */<br />public static class SavedState extends BaseSavedState {<br />int currentScreen = -1;</p><p>SavedState(Parcelable superState) {<br />super(superState);<br />}</p><p>private SavedState(Parcel in) {<br />super(in);<br />currentScreen = in.readInt();<br />}</p><p>public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {<br />public SavedState createFromParcel(Parcel in) {// get data written by Parcelable.writeToParcel()<br />return new SavedState(in);<br />}</p><p>public SavedState[] newArray(int size) {// create array of the Parcelable<br />return new SavedState[size];<br />}<br />};</p><p>@Override<br />public void writeToParcel(Parcel out, int flags) {// set data to parcel<br />super.writeToParcel(out, flags);<br />out.writeInt(currentScreen);<br />}</p><p>}</p><p>@Override<br />protected Parcelable onSaveInstanceState() {// 儲存狀態<br />final SavedState state = new SavedState(super.onSaveInstanceState());<br />state.currentScreen = mCurrentPage; // save InstanceState<br />return state;<br />}</p><p>@Override<br />protected void onRestoreInstanceState(Parcelable state) {// 恢複狀態<br />SavedState savedState = (SavedState) state;<br />super.onRestoreInstanceState(savedState.getSuperState());// get InstanceState<br />if (savedState.currentScreen != INVALID_SCREEN) {<br />mCurrentPage = savedState.currentScreen;<br />}<br />}
D、容器事件監聽介面
public void addOnScrollListener(OnScrollListener listener) {<br />mListeners.add(listener);<br />}</p><p>public void removeOnScrollListener(OnScrollListener listener) {<br />mListeners.remove(listener);<br />}</p><p>/** 自訂介面 */<br />public static interface OnScrollListener {<br />void onScroll(int scrollX);<br />void onViewScrollFinished(int currentPage);<br />}
代碼下載
參考推薦:
Android中自訂屬性的使用
Android中自訂屬性的格式詳解
Scroller(Android) Scroller(cnblog)
Android Parcelable
Android左右滑動載入分頁