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

来源:互联网 发布:去哪里投诉淘宝卖家 编辑:程序博客网 时间:2024/06/05 04:50

IT程序员开发必备-各类资源下载清单,史上最全IT资源,个人收藏总结!


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


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



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

1、自定义属性

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

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

[html] view plaincopyprint?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <declare-styleable name="com.myapps.widget.Pager">  
  4.         <attr name="pageWidth" format="dimension" />  
  5.     </declare-styleable>  
  6.       
  7.     <declare-styleable name="com.myapps.widget.PagerBar">  
  8.         <attr name="barColor" format="color" />  
  9.         <attr name="highlightColor" format="color" />  
  10.         <attr name="fadeDelay" format="integer" />  
  11.         <attr name="fadeDuration" format="integer" />  
  12.         <attr name="roundRectRadius" format="dimension" />  
  13.     </declare-styleable>  
  14. </resources>  

然后,在自定义控件的代码中,解析自定义的属性,如在PagerBar.java:
[java] view plaincopyprint?
  1. // 自定义属性  
  2.         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_PagerBar);  
  3.         int barBackColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_barColor, DEFAULT_BAR_BACKCOLOR);              // bar背景色  
  4.         int barForeColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_highlightColor, DEFAULT_BAR_FORECOLOR);    // bar前景色  
  5.         fadeDelay = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDelay, DEFAULT_FADE_DELAY);             // bar消失延迟时间  
  6.         fadeDuration = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDuration, DEFAULT_FADE_DURATION);    // bar消失动画时间  
  7.         ovalRadius = a.getDimension(R.styleable.com_myapps_widget_PagerBar_roundRectRadius, 2f);  
  8.         a.recycle();  

最后,在布局文件中设置属性,如在main.xml
[html] view plaincopyprint?
  1. <com.homer.mycontrol.PagerBar  
  2.     android:id="@+id/control"  
  3.     android:layout_width="fill_parent"  
  4.     android:layout_height="4dip"  
  5.     android:layout_margin="8dip"  
  6.     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)中的下部银白色细条,具体实现:

[java] view plaincopyprint?
  1. public PagerBar(Context context, AttributeSet attrs, int defStyle) {  
  2.     super(context, attrs, defStyle);  
  3.   
  4.     // 自定义属性  
  5.     TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_PagerBar);  
  6.     int barBackColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_barColor, DEFAULT_BAR_BACKCOLOR);              // bar背景色  
  7.     int barForeColor = a.getColor(R.styleable.com_myapps_widget_PagerBar_highlightColor, DEFAULT_BAR_FORECOLOR);    // bar前景色  
  8.     fadeDelay = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDelay, DEFAULT_FADE_DELAY);             // bar消失延迟时间  
  9.     fadeDuration = a.getInteger(R.styleable.com_myapps_widget_PagerBar_fadeDuration, DEFAULT_FADE_DURATION);    // bar消失动画时间  
  10.     ovalRadius = a.getDimension(R.styleable.com_myapps_widget_PagerBar_roundRectRadius, 2f);  
  11.     a.recycle();  
  12.   
  13.     barBackPaint = new Paint();  
  14.     barBackPaint.setColor(barBackColor);  
  15.   
  16.     barForePaint = new Paint();  
  17.     barForePaint.setColor(barForeColor);  
  18.   
  19.     fadeOutAnimation = new AlphaAnimation(1f, 0f);  
  20.     fadeOutAnimation.setDuration(fadeDuration);  
  21.     fadeOutAnimation.setRepeatCount(0);  
  22.     fadeOutAnimation.setInterpolator(new LinearInterpolator());  
  23.     fadeOutAnimation.setFillEnabled(true);  
  24.     fadeOutAnimation.setFillAfter(true);  
  25. }  
  26.   
  27. public int getNumPages() {  
  28.     return numPages;  
  29. }  
  30.   
  31. public void setNumPages(int numPages) {  
  32.     if (numPages <= 0) {  
  33.         throw new IllegalArgumentException("numPages must be positive");  
  34.     }  
  35.     this.numPages = numPages;  
  36.     invalidate();       // 重绘View  
  37.     fadeOut();          // 设置bar消失效果  
  38. }  
  39.   
  40. /** bar消失动画 */  
  41. private void fadeOut() {  
  42.     if (fadeDuration > 0) {  
  43.         clearAnimation();  
  44.         fadeOutAnimation.setStartTime(AnimationUtils.currentAnimationTimeMillis() + fadeDelay); //延迟fadeDelay后动画开始  
  45.         setAnimation(fadeOutAnimation);  
  46.     }  
  47. }  
  48.   
  49. /**  @return  0 to numPages-1 */  
  50. public int getCurrentPage() {  
  51.     return currentPage;  
  52. }  
  53.   
  54. /** @param currentPage  0 to numPages-1  */  
  55. public void setCurrentPage(int currentPage) {  
  56.     if (currentPage < 0 || currentPage >= numPages) {  
  57.         throw new IllegalArgumentException("currentPage parameter out of bounds");  
  58.     }  
  59.     if (this.currentPage != currentPage) {  
  60.         this.currentPage = currentPage;  
  61.         this.position = currentPage * getPageWidth();   // bar前景色滑动条的起始位置(像素值)  
  62.         invalidate();  
  63.         fadeOut();  
  64.     }  
  65. }  
  66.   
  67. /** 获取View的宽度,即bar的宽度 */  
  68. public int getPageWidth() {  
  69.     return getWidth() / numPages;   // getWidth()是PagerBar的宽度(减去了margin左右距离后)  
  70. }  
  71.   
  72. /**  @param position     can be -pageWidth to pageWidth*(numPages+1)  */  
  73. public void setPosition(int position) {  
  74.     if (this.position != position) {  
  75.         this.position = position;  
  76.         invalidate();  
  77.         fadeOut();  
  78.     }  
  79. }  
  80.   
  81. @Override  
  82. protected void onDraw(Canvas canvas) {  
  83.     canvas.drawRoundRect(new RectF(00, getWidth(), getHeight()), ovalRadius, ovalRadius, barBackPaint);   // 绘制bar背景  
  84.     canvas.drawRoundRect(new RectF(position, 0, position + (getWidth() / numPages), getHeight()), ovalRadius, ovalRadius, barForePaint);    // 绘制bar前景  
  85. }  


3、自定义控件Pager

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

A、自定义属性解析

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

C、容器状态保存(onSaveInstanceState)

D、容器事件监听接口


详细实现如下:

A、自定义属性解析

[java] view plaincopyprint?
  1. public Pager(Context context, AttributeSet attrs, int defStyle) {  
  2.     super(context, attrs, defStyle);  
  3.   
  4.     // 自定义属性  
  5.     TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_Pager);  
  6.     pageWidthSpec = a.getDimensionPixelSize(R.styleable.com_myapps_widget_Pager_pageWidth, SPEC_UNDEFINED);  
  7.     a.recycle();  
  8. }  


B、Pager容器控件Scroller滑动页设置与控制
[java] view plaincopyprint?
  1. public void setCurrentPage(int currentPage) {  
  2.     mCurrentPage = Math.max(0, Math.min(currentPage, getChildCount()));     // 非常好  
  3.     scrollTo(getScrollXForPage(mCurrentPage), 0);  
  4.     invalidate();   // 重绘View  
  5. }  
  6.   
  7. int getCurrentPage() {  
  8.     return mCurrentPage;  
  9. }  
  10.   
  11. public void setPageWidth(int pageWidth) {  
  12.     this.pageWidthSpec = pageWidth;  
  13. }  
  14.   
  15. public int getPageWidth() {  
  16.     return pageWidth;  
  17. }  
  18.   
  19. /** 获取whichPage的Pager起始x位置,whichPage从0开始计 */  
  20. private int getScrollXForPage(int whichPage) {  
  21.     return (whichPage * pageWidth) - pageWidthPadding();  
  22. }  
  23.   
  24. /** 返回View的 paddingwidth 一半(1/2)*/  
  25. int pageWidthPadding() {  
  26.     return ((getMeasuredWidth() - pageWidth) / 2);  
  27. }  
  28.   
  29. @Override  
  30. public void computeScroll() {       // update  mScrollX and mScrollY  of View  
  31.     if (mScroller.computeScrollOffset()) {  
  32.         scrollTo(mScroller.getCurrX(), mScroller.getCurrY());  
  33.         postInvalidate();           // invalidate the View from a non-UI thread  
  34.     } else if (mNextPage != INVALID_SCREEN) {  
  35.         mCurrentPage = mNextPage;  
  36.         mNextPage = INVALID_SCREEN;  
  37.         clearChildrenCache();  
  38.     }  
  39. }  
  40.   
  41. @Override  
  42. protected void dispatchDraw(Canvas canvas) {    // draw the child views  
  43.       
  44.     final long drawingTime = getDrawingTime();  // 绘制childView  
  45.     final int count = getChildCount();  
  46.     for (int i = 0; i < count; i++) {  
  47.         drawChild(canvas, getChildAt(i), drawingTime);  
  48.     }  
  49.   
  50.     for (OnScrollListener mListener : mListeners) { // 自定义接口  
  51.         int adjustedScrollX = getScrollX() + pageWidthPadding();  
  52.         mListener.onScroll(adjustedScrollX);  
  53.         if (adjustedScrollX % pageWidth == 0) { // scroll finished  
  54.             mListener.onViewScrollFinished(adjustedScrollX / pageWidth);  
  55.         }  
  56.     }  
  57. }  
  58.   
  59. @Override  
  60. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  61.     super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  62.   
  63.     pageWidth = (pageWidthSpec == SPEC_UNDEFINED) ? getMeasuredWidth() : pageWidthSpec;  
  64.     pageWidth = Math.min(pageWidth, getMeasuredWidth());  
  65.   
  66.     final int count = getChildCount();  
  67.     for (int i = 0; i < count; i++) {  
  68.         widthMeasureSpec = MeasureSpec.makeMeasureSpec(pageWidth, MeasureSpec.EXACTLY);  
  69.         getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);  
  70.     }  
  71.   
  72.     if (mFirstLayout) { // 第一次显示Pager时,page的位置  
  73.         scrollTo(getScrollXForPage(mCurrentPage), mScroller.getCurrY());  
  74.         mFirstLayout = false;  
  75.     }  
  76. }  
  77.   
  78. @Override  
  79. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {  
  80.     int childLeft = 0;  
  81.   
  82.     final int count = getChildCount();  // 绘制childView  
  83.     for (int i = 0; i < count; i++) {  
  84.         final View child = getChildAt(i);  
  85.         if (child.getVisibility() != View.GONE) {  
  86.             final int childWidth = child.getMeasuredWidth();  
  87.             child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());  
  88.             childLeft += childWidth;  
  89.         }  
  90.     }  
  91. }  

[java] view plaincopyprint?
  1. @Override  
  2. public boolean onInterceptTouchEvent(MotionEvent ev) {  
  3.     final int action = ev.getAction();  
  4.     if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) { // 正在滑动中  
  5.         return true;  
  6.     }  
  7.   
  8.     final float x = ev.getX();  
  9.     final float y = ev.getY();  
  10.   
  11.     switch (action) {  
  12.     case MotionEvent.ACTION_MOVE:  
  13.         if (mTouchState == TOUCH_STATE_REST) {  
  14.             checkStartScroll(x, y);  
  15.         }  
  16.         break;  
  17.   
  18.     case MotionEvent.ACTION_DOWN:  
  19.         mLastMotionX = x;  
  20.         mLastMotionY = y;  
  21.         mAllowLongPress = true;  
  22.   
  23.         mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;    // scroll 完成后,重置状态  
  24.         break;  
  25.   
  26.     case MotionEvent.ACTION_CANCEL:  
  27.     case MotionEvent.ACTION_UP:  
  28.         clearChildrenCache();  
  29.         mTouchState = TOUCH_STATE_REST;  
  30.         break;  
  31.     }  
  32.   
  33.     return mTouchState != TOUCH_STATE_REST;  
  34. }  
  35.   
  36. @Override  
  37. public boolean onTouchEvent(MotionEvent ev) {  
  38.     if (mVelocityTracker == null) {  
  39.         mVelocityTracker = VelocityTracker.obtain();  
  40.     }  
  41.     mVelocityTracker.addMovement(ev);  
  42.   
  43.     final int action = ev.getAction();  
  44.     final float x = ev.getX();  
  45.     final float y = ev.getY();  
  46.   
  47.     switch (action) {  
  48.     case MotionEvent.ACTION_DOWN:  
  49.         if (!mScroller.isFinished()) {  
  50.             mScroller.abortAnimation();  
  51.         }  
  52.         mLastMotionX = x;  
  53.         break;  
  54.           
  55.     case MotionEvent.ACTION_MOVE:  
  56.         if (mTouchState == TOUCH_STATE_REST) {  
  57.             checkStartScroll(x, y);  
  58.         } else if (mTouchState == TOUCH_STATE_SCROLLING) {  // scrolling 状态时,重绘view  
  59.             int deltaX = (int) (mLastMotionX - x);  
  60.             mLastMotionX = x;  
  61.   
  62.             if (getScrollX() < 0 || getScrollX() > getChildAt(getChildCount() - 1).getLeft()) {  
  63.                 deltaX /= 2;  
  64.             }  
  65.             scrollBy(deltaX, 0);  
  66.         }  
  67.         break;  
  68.           
  69.     case MotionEvent.ACTION_UP:  
  70.         if (mTouchState == TOUCH_STATE_SCROLLING) {  
  71.             final VelocityTracker velocityTracker = mVelocityTracker;  
  72.             velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);  
  73.             int velocityX = (int) velocityTracker.getXVelocity();  
  74.   
  75.             if (velocityX > SNAP_VELOCITY && mCurrentPage > 0) {  
  76.                 snapToPage(mCurrentPage - 1);  
  77.             } else if (velocityX < -SNAP_VELOCITY && mCurrentPage < getChildCount() - 1) {  
  78.                 snapToPage(mCurrentPage + 1);  
  79.             } else {  
  80.                 snapToDestination();  
  81.             }  
  82.   
  83.             if (mVelocityTracker != null) {  
  84.                 mVelocityTracker.recycle();  
  85.                 mVelocityTracker = null;  
  86.             }  
  87.         }  
  88.         mTouchState = TOUCH_STATE_REST;  
  89.         break;  
  90.     case MotionEvent.ACTION_CANCEL:  
  91.         mTouchState = TOUCH_STATE_REST;  
  92.     }  
  93.   
  94.     return true;  
  95. }  
  96.   
  97. /** 检查scroll状态,并设置绘制scroll缓存 */  
  98. private void checkStartScroll(float x, float y) {  
  99.     final int xDiff = (int) Math.abs(x - mLastMotionX);  
  100.     final int yDiff = (int) Math.abs(y - mLastMotionY);  
  101.   
  102.     boolean xMoved = xDiff > mTouchSlop;  
  103.     boolean yMoved = yDiff > mTouchSlop;  
  104.   
  105.     if (xMoved || yMoved) {  
  106.         if (xMoved) {  
  107.             mTouchState = TOUCH_STATE_SCROLLING;        // 设置为scrolling 状态  
  108.             enableChildrenCache();  
  109.         }  
  110.         if (mAllowLongPress) {  
  111.             mAllowLongPress = false;  
  112.             final View currentScreen = getChildAt(mCurrentPage);  
  113.             currentScreen.cancelLongPress();    // Cancels a pending long press  
  114.         }  
  115.     }  
  116. }  
  117.   
  118. void enableChildrenCache() {  
  119.     setChildrenDrawingCacheEnabled(true);       // Enables or disables the drawing cache for each child of this viewGroup  
  120.     setChildrenDrawnWithCacheEnabled(true); // Tells the ViewGroup to draw its children using their drawing cache  
  121. }  
  122.   
  123. void clearChildrenCache() {  
  124.     setChildrenDrawnWithCacheEnabled(false);  
  125. }  
  126.   
  127. private void snapToDestination() {  
  128.     final int startX = getScrollXForPage(mCurrentPage);  
  129.     int whichPage = mCurrentPage;  
  130.     if (getScrollX() < startX - getWidth() / 8) {  
  131.         whichPage = Math.max(0, whichPage - 1);  
  132.     } else if (getScrollX() > startX + getWidth() / 8) {  
  133.         whichPage = Math.min(getChildCount() - 1, whichPage + 1);  
  134.     }  
  135.   
  136.     snapToPage(whichPage);  
  137. }  
  138.   
  139. void snapToPage(int whichPage) {  
  140.     enableChildrenCache();  
  141.   
  142.     boolean changingPages = whichPage != mCurrentPage;  
  143.   
  144.     mNextPage = whichPage;  
  145.   
  146.     View focusedChild = getFocusedChild();  
  147.     if (focusedChild != null && changingPages && focusedChild == getChildAt(mCurrentPage)) {  
  148.         focusedChild.clearFocus();  
  149.     }  
  150.   
  151.     final int newX = getScrollXForPage(whichPage);  
  152.     final int delta = newX - getScrollX();  
  153.     mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);  
  154.     invalidate();  
  155. }  
  156.   
  157. /** 向左滑动 */  
  158. public void scrollLeft() {  
  159.     if (mNextPage == INVALID_SCREEN && mCurrentPage > 0 && mScroller.isFinished()) {  
  160.         snapToPage(mCurrentPage - 1);  
  161.     }  
  162. }  
  163.   
  164. /** 向右滑动 */  
  165. public void scrollRight() {  
  166.     if (mNextPage == INVALID_SCREEN && mCurrentPage < getChildCount() - 1 && mScroller.isFinished()) {  
  167.         snapToPage(mCurrentPage + 1);  
  168.     }  
  169. }  


C、容器状态保存(onSaveInstanceState)

[java] view plaincopyprint?
  1. /** 保存状态 */  
  2. public static class SavedState extends BaseSavedState {  
  3.     int currentScreen = -1;  
  4.   
  5.     SavedState(Parcelable superState) {  
  6.         super(superState);  
  7.     }  
  8.   
  9.     private SavedState(Parcel in) {  
  10.         super(in);  
  11.         currentScreen = in.readInt();  
  12.     }  
  13.   
  14.     public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {  
  15.         public SavedState createFromParcel(Parcel in) { // get data written by Parcelable.writeToParcel()     
  16.             return new SavedState(in);  
  17.         }  
  18.   
  19.         public SavedState[] newArray(int size) {            // create  array of the Parcelable   
  20.             return new SavedState[size];  
  21.         }  
  22.     };  
  23.       
  24.     @Override  
  25.     public void writeToParcel(Parcel out, int flags) {      // set data to parcel  
  26.         super.writeToParcel(out, flags);  
  27.         out.writeInt(currentScreen);  
  28.     }  
  29.   
  30. }  
  31.   
  32. @Override  
  33. protected Parcelable onSaveInstanceState() {        // 保存状态  
  34.     final SavedState state = new SavedState(super.onSaveInstanceState());  
  35.     state.currentScreen = mCurrentPage;     // save InstanceState  
  36.     return state;  
  37. }  
  38.   
  39. @Override  
  40. protected void onRestoreInstanceState(Parcelable state) {   // 恢复状态  
  41.     SavedState savedState = (SavedState) state;  
  42.     super.onRestoreInstanceState(savedState.getSuperState());   // get InstanceState  
  43.     if (savedState.currentScreen != INVALID_SCREEN) {  
  44.         mCurrentPage = savedState.currentScreen;      
  45.     }  
  46. }  

D、容器事件监听接口

[java] view plaincopyprint?
  1. public void addOnScrollListener(OnScrollListener listener) {  
  2.     mListeners.add(listener);  
  3. }  
  4.   
  5. public void removeOnScrollListener(OnScrollListener listener) {  
  6.     mListeners.remove(listener);  
  7. }  
  8.   
  9. /** 自定义接口 */  
  10. public static interface OnScrollListener {  
  11.     void onScroll(int scrollX);  
  12.     void onViewScrollFinished(int currentPage);  
  13. }  


代码下载



参考推荐:

Android中自定义属性的使用

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

Scroller(Android)  Scroller(cnblog)

Android Parcelable

Android左右滑动加载分页


原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 夏天车间太热怎么办 封印者加载不了怎么办 雪铁龙凯旋后备箱打不开怎么办 迅雷9无法加速怎么办 小孩不会拍球怎么办 巨细胞亲和力低怎么办 职工监事离职了怎么办 高考怎么加分怎么办了 荣耀战魂崩溃怎么办 鲅鱼圈二手房房产证怎么办 画面留白太多怎么办 摔倒脖子扭了怎么办 面试问特长没有怎么办 组织一个演讲 你怎么办 跑步把腿跑粗了怎么办 跑步跑出肌肉腿怎么办 班级聚会人不齐怎么办 如果我们分手了怎么办 幼儿园课上完了怎么办 第一次上台唱歌紧张怎么办 第一次当主持人紧张怎么办 主持的时候口吃怎么办 打官司公司改规章制度怎么办 怎么办生日派对比较好 考不过科目一怎么办 卫校毕业证丢了怎么办 不小心摔跤了怎么办 篮球打气口漏气怎么办 手机麦有回音怎么办 obs直播有杂音怎么办 耳机会有回音怎么办 眼睛看东西散光怎么办 一只眼睛红肿怎么办 幼儿精力不集中怎么办 孕妇照四维说头大怎么办 孕晚期贫血严重怎么办 孕期肚子胀气难受怎么办 结婚三月没怀孕怎么办 在小龙武校病了怎么办 地下厨房太闷热怎么办 菜刀刀背磨手怎么办