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

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. TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.com_myapps_widget_Pager);
  5. pageWidthSpec = a.getDimensionPixelSize(R.styleable.com_myapps_widget_Pager_pageWidth, SPEC_UNDEFINED);
  6. a.recycle();
  7. }

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. int getCurrentPage() {
  7. return mCurrentPage;
  8. }
  9. public void setPageWidth(int pageWidth) {
  10. this.pageWidthSpec = pageWidth;
  11. }
  12. public int getPageWidth() {
  13. return pageWidth;
  14. }
  15. /** 获取whichPage的Pager起始x位置,whichPage从0开始计 */
  16. private int getScrollXForPage(int whichPage) {
  17. return (whichPage * pageWidth) - pageWidthPadding();
  18. }
  19. /** 返回View的 paddingwidth 一半(1/2)*/
  20. int pageWidthPadding() {
  21. return ((getMeasuredWidth() - pageWidth) / 2);
  22. }
  23. @Override
  24. public void computeScroll() {       // update  mScrollX and mScrollY  of View
  25. if (mScroller.computeScrollOffset()) {
  26. scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
  27. postInvalidate();           // invalidate the View from a non-UI thread
  28. } else if (mNextPage != INVALID_SCREEN) {
  29. mCurrentPage = mNextPage;
  30. mNextPage = INVALID_SCREEN;
  31. clearChildrenCache();
  32. }
  33. }
  34. @Override
  35. protected void dispatchDraw(Canvas canvas) {    // draw the child views
  36. final long drawingTime = getDrawingTime();  // 绘制childView
  37. final int count = getChildCount();
  38. for (int i = 0; i < count; i++) {
  39. drawChild(canvas, getChildAt(i), drawingTime);
  40. }
  41. for (OnScrollListener mListener : mListeners) { // 自定义接口
  42. int adjustedScrollX = getScrollX() + pageWidthPadding();
  43. mListener.onScroll(adjustedScrollX);
  44. if (adjustedScrollX % pageWidth == 0) { // scroll finished
  45. mListener.onViewScrollFinished(adjustedScrollX / pageWidth);
  46. }
  47. }
  48. }
  49. @Override
  50. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  51. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  52. pageWidth = (pageWidthSpec == SPEC_UNDEFINED) ? getMeasuredWidth() : pageWidthSpec;
  53. pageWidth = Math.min(pageWidth, getMeasuredWidth());
  54. final int count = getChildCount();
  55. for (int i = 0; i < count; i++) {
  56. widthMeasureSpec = MeasureSpec.makeMeasureSpec(pageWidth, MeasureSpec.EXACTLY);
  57. getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
  58. }
  59. if (mFirstLayout) { // 第一次显示Pager时,page的位置
  60. scrollTo(getScrollXForPage(mCurrentPage), mScroller.getCurrY());
  61. mFirstLayout = false;
  62. }
  63. }
  64. @Override
  65. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  66. int childLeft = 0;
  67. final int count = getChildCount();  // 绘制childView
  68. for (int i = 0; i < count; i++) {
  69. final View child = getChildAt(i);
  70. if (child.getVisibility() != View.GONE) {
  71. final int childWidth = child.getMeasuredWidth();
  72. child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
  73. childLeft += childWidth;
  74. }
  75. }
  76. }
  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. final float x = ev.getX();
  8. final float y = ev.getY();
  9. switch (action) {
  10. case MotionEvent.ACTION_MOVE:
  11. if (mTouchState == TOUCH_STATE_REST) {
  12. checkStartScroll(x, y);
  13. }
  14. break;
  15. case MotionEvent.ACTION_DOWN:
  16. mLastMotionX = x;
  17. mLastMotionY = y;
  18. mAllowLongPress = true;
  19. mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;    // scroll 完成后,重置状态
  20. break;
  21. case MotionEvent.ACTION_CANCEL:
  22. case MotionEvent.ACTION_UP:
  23. clearChildrenCache();
  24. mTouchState = TOUCH_STATE_REST;
  25. break;
  26. }
  27. return mTouchState != TOUCH_STATE_REST;
  28. }
  29. @Override
  30. public boolean onTouchEvent(MotionEvent ev) {
  31. if (mVelocityTracker == null) {
  32. mVelocityTracker = VelocityTracker.obtain();
  33. }
  34. mVelocityTracker.addMovement(ev);
  35. final int action = ev.getAction();
  36. final float x = ev.getX();
  37. final float y = ev.getY();
  38. switch (action) {
  39. case MotionEvent.ACTION_DOWN:
  40. if (!mScroller.isFinished()) {
  41. mScroller.abortAnimation();
  42. }
  43. mLastMotionX = x;
  44. break;
  45. case MotionEvent.ACTION_MOVE:
  46. if (mTouchState == TOUCH_STATE_REST) {
  47. checkStartScroll(x, y);
  48. } else if (mTouchState == TOUCH_STATE_SCROLLING) {  // scrolling 状态时,重绘view
  49. int deltaX = (int) (mLastMotionX - x);
  50. mLastMotionX = x;
  51. if (getScrollX() < 0 || getScrollX() > getChildAt(getChildCount() - 1).getLeft()) {
  52. deltaX /= 2;
  53. }
  54. scrollBy(deltaX, 0);
  55. }
  56. break;
  57. case MotionEvent.ACTION_UP:
  58. if (mTouchState == TOUCH_STATE_SCROLLING) {
  59. final VelocityTracker velocityTracker = mVelocityTracker;
  60. velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
  61. int velocityX = (int) velocityTracker.getXVelocity();
  62. if (velocityX > SNAP_VELOCITY && mCurrentPage > 0) {
  63. snapToPage(mCurrentPage - 1);
  64. } else if (velocityX < -SNAP_VELOCITY && mCurrentPage < getChildCount() - 1) {
  65. snapToPage(mCurrentPage + 1);
  66. } else {
  67. snapToDestination();
  68. }
  69. if (mVelocityTracker != null) {
  70. mVelocityTracker.recycle();
  71. mVelocityTracker = null;
  72. }
  73. }
  74. mTouchState = TOUCH_STATE_REST;
  75. break;
  76. case MotionEvent.ACTION_CANCEL:
  77. mTouchState = TOUCH_STATE_REST;
  78. }
  79. return true;
  80. }
  81. /** 检查scroll状态,并设置绘制scroll缓存 */
  82. private void checkStartScroll(float x, float y) {
  83. final int xDiff = (int) Math.abs(x - mLastMotionX);
  84. final int yDiff = (int) Math.abs(y - mLastMotionY);
  85. boolean xMoved = xDiff > mTouchSlop;
  86. boolean yMoved = yDiff > mTouchSlop;
  87. if (xMoved || yMoved) {
  88. if (xMoved) {
  89. mTouchState = TOUCH_STATE_SCROLLING;        // 设置为scrolling 状态
  90. enableChildrenCache();
  91. }
  92. if (mAllowLongPress) {
  93. mAllowLongPress = false;
  94. final View currentScreen = getChildAt(mCurrentPage);
  95. currentScreen.cancelLongPress();    // Cancels a pending long press
  96. }
  97. }
  98. }
  99. void enableChildrenCache() {
  100. setChildrenDrawingCacheEnabled(true);       // Enables or disables the drawing cache for each child of this viewGroup
  101. setChildrenDrawnWithCacheEnabled(true); // Tells the ViewGroup to draw its children using their drawing cache
  102. }
  103. void clearChildrenCache() {
  104. setChildrenDrawnWithCacheEnabled(false);
  105. }
  106. private void snapToDestination() {
  107. final int startX = getScrollXForPage(mCurrentPage);
  108. int whichPage = mCurrentPage;
  109. if (getScrollX() < startX - getWidth() / 8) {
  110. whichPage = Math.max(0, whichPage - 1);
  111. } else if (getScrollX() > startX + getWidth() / 8) {
  112. whichPage = Math.min(getChildCount() - 1, whichPage + 1);
  113. }
  114. snapToPage(whichPage);
  115. }
  116. void snapToPage(int whichPage) {
  117. enableChildrenCache();
  118. boolean changingPages = whichPage != mCurrentPage;
  119. mNextPage = whichPage;
  120. View focusedChild = getFocusedChild();
  121. if (focusedChild != null && changingPages && focusedChild == getChildAt(mCurrentPage)) {
  122. focusedChild.clearFocus();
  123. }
  124. final int newX = getScrollXForPage(whichPage);
  125. final int delta = newX - getScrollX();
  126. mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 2);
  127. invalidate();
  128. }
  129. /** 向左滑动 */
  130. public void scrollLeft() {
  131. if (mNextPage == INVALID_SCREEN && mCurrentPage > 0 && mScroller.isFinished()) {
  132. snapToPage(mCurrentPage - 1);
  133. }
  134. }
  135. /** 向右滑动 */
  136. public void scrollRight() {
  137. if (mNextPage == INVALID_SCREEN && mCurrentPage < getChildCount() - 1 && mScroller.isFinished()) {
  138. snapToPage(mCurrentPage + 1);
  139. }
  140. }

C、容器状态保存(onSaveInstanceState)

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

D、容器事件监听接口

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

Android 滑动效果高级篇(八)—— 自定义控件的更多相关文章

  1. Android 滑动效果高级篇(七)—— 华丽翻页效果

    By 何明桂(http://blog.csdn.net/hmg25) 转载请注明出处 之前看到像ipad上的ibook的模拟书籍翻页的特效感觉很炫,在android上也有像laputa和ireader ...

  2. Android 滑动效果进阶篇(六)—— 倒影效果

    上篇介绍了使用Animation实现3D动画旋转翻页效果,现在介绍图片倒影实现,先看效果图 本示例主要通过自定义Gallery和ImageAdapter(继承自BaseAdapter)实现 1.倒影绘 ...

  3. Android 滑动效果入门篇(二)—— Gallery

    Gallery 是Android官方提供的一个View容器类,继承于AbsSpinner类,用于实现页面滑动效果. 从上面的继承关系可以看出,AbsSpinner类继承自AdapterView,因此我 ...

  4. Android 滑动效果入门篇(一)—— ViewFlipper

    ViewFilpper 是Android官方提供的一个View容器类,继承于ViewAnimator类,用于实现页面切换,也可以设定时间间隔,让它自动播放.又ViewAnimator继承至于Frame ...

  5. Android 滑动效果进阶篇(五)—— 3D旋转

    前面介绍了利用Android自带的控件,进行滑动翻页制作效果,现在我们通过代码实现一些滑动翻页的动画效果. Animation实现动画有两个方式:帧动画(frame-by-frame animatio ...

  6. Android 滑动效果基础篇(四)—— Gallery + GridView

    Android系统自带一个GridView和Gallery两个控件,GridView网格显示,Gallery单个浏览,两者结合起来可以真正实现Gallery浏览图片效果. 本示例通过GridView和 ...

  7. Android 滑动效果基础篇(三)—— Gallery仿图像集浏览

    Android系统自带一个Gallery浏览图片的应用,通过手指拖动时能够非常流畅的显示图片,用户交互和体验都很好. 本示例就是通过Gallery和自定义的View,模仿实现一个仿Gallery图像集 ...

  8. 十六、Android 滑动效果汇总

    Android 滑动效果入门篇(一)—— ViewFlipper Android 滑动效果入门篇(二)—— Gallery Android 滑动效果基础篇(三)—— Gallery仿图像集浏览 And ...

  9. Android 滑动效果汇总

    Android 滑动效果入门篇(一)—— ViewFlipper Android 滑动效果入门篇(二)—— Gallery Android 滑动效果基础篇(三)—— Gallery仿图像集浏览 And ...

随机推荐

  1. linux命令——磁盘管理cd

    Linux cd 命令可以说是Linux中最基本的命令语句,其他的命令语句要进行操作,都是建立在使用 cd 命令上的. cd指令可让用户在不同的目录间切换,但该用户必须拥有足够的权限进入目的目录. 1 ...

  2. Perfect Service

    题意: n个节点树,在一个节点放上一台服务器可以给相邻的其他各点提供服务且一个节点只能接受一台服务器,求使n个节点都被服务放的服务器的最小数量. 分析: 不算太难,状态想的差不多,但是考虑不全面状态方 ...

  3. HTTP协议学习笔记-2

    HTTP报文 HTTP报文分为请求报文和响应报文(requeset and response) 请求报文的格式: <method>  <request -URL> <ve ...

  4. 使用七牛云存储----大家自己的图床[python]

    ##写博客什么的总得贴图吧,图床选来选去还是七牛吧.嗯,就是你了 [OSchaina 源码] 结合FastStone Capture 简直爽歪歪. FastStone Capture 自动保存图片到文 ...

  5. How to interact with the Chef Server using the Chef Server API using Shell script

    !/usr/bin/env bash   _chef_dir () { # Helper function: # Recursive function that searches for chef c ...

  6. How to fix “X: user not authorized to run the X server, aborting.”? -摘自网络

    This is just a simple tips to solve a error message when you start your X session with “startx” comm ...

  7. ehcache 的配置

    配置:一.在src目录下加入ehcache.xml: <cache name="SimplePageCachingFilter" maxElementsInMemory=&q ...

  8. document.documentElement.clientHeight||document.documentElement.scrollHeight

    在我看<JavaScript高级程序设计>(第三版)的时候,在clientHeight和scrollHeight那部分把我弄糊涂了. 原书是这样写的:( //宽度同理,就不仔细描述了.) ...

  9. Oracle的回收站和闪回查询机制(二)

    上一篇中讲诉了Oracle中一些闪回查询(Flashback Query),这是利用回滚段信息来恢复一个或一些表到以前的一个时间点(一个快照).要注意的是,Flashback Query仅仅是查询以前 ...

  10. Spring dependency checking with @Required Annotation

    Spring's dependency checking in bean configuration file is used to make sure all properties of a cer ...