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

来源:互联网 发布:movist for mac 1.4.2 编辑:程序博客网 时间:2024/05/22 10:48

自定义控件,较常用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();// 重绘ViewfadeOut();// 设置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();}}@Overrideprotected 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);}@Overridepublic void computeScroll() {// update  mScrollX and mScrollY  of Viewif (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();}}@Overrideprotected void dispatchDraw(Canvas canvas) {// draw the child viewsfinal long drawingTime = getDrawingTime();// 绘制childViewfinal 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 finishedmListener.onViewScrollFinished(adjustedScrollX / pageWidth);}}}@Overrideprotected 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;}}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {int childLeft = 0;final int count = getChildCount();// 绘制childViewfor (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;}}}

@Overridepublic 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;}@Overridepublic 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 状态时,重绘viewint 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 viewGroupsetChildrenDrawnWithCacheEnabled(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];}};@Overridepublic void writeToParcel(Parcel out, int flags) {// set data to parcelsuper.writeToParcel(out, flags);out.writeInt(currentScreen);}}@Overrideprotected Parcelable onSaveInstanceState() {// 保存状态final SavedState state = new SavedState(super.onSaveInstanceState());state.currentScreen = mCurrentPage; // save InstanceStatereturn state;}@Overrideprotected void onRestoreInstanceState(Parcelable state) {// 恢复状态SavedState savedState = (SavedState) state;super.onRestoreInstanceState(savedState.getSuperState());// get InstanceStateif (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左右滑动加载分页