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


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

     

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

     

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

    1、自定义属性

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

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

    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:

    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

    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)中的下部银白色细条,具体实现:

    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、自定义属性解析

    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滑动页设置与控制

    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. }  
    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)

    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、容器事件监听接口

      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);  

  • 相关阅读:
    动态规划-1维消消乐
    矩阵求幂-倍加算法
    动态规划-匹配问题
    动态规划-最短回文串
    动态规划-最长回文子串
    动态规划-矩形嵌套
    动态规划-硬币找零
    windows 2003最完善最完美的权限及安全设置解决方案【转】
    python模块之email: 电子邮件编码
    word页面设置问题。通过域设置首页不计算页面的自定义页码格式
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/3585826.html
Copyright © 2020-2023  润新知