自定义控件,较常用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. 解决:Unable to connect to repository https://dl-ssl.google.com/android/eclipse/site.xml

    ailed to fectch URl https://dl-ssl.google.com/android/repository/addons_list.xml, reason: Connection ...

  2. andriod的简单用法2

    1.在Activity中使用menu //创建菜单项 public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this ...

  3. wuzhicms访问统计实现方法

    实现目标:程序实现了对整站页面pv的统计文件的位置:coreframe/app/content/pv.php代码预览: /** * 总站访问次数统计 */ defined('IN_WZ') or ex ...

  4. 树莓派I2C连接18B20

    按图连接设备 载入模块 sudo modprobe w1-gpio sudo modprobe w1-therm cd /sys/bus/w1/devices/ 显示结果 ls pi@raspberr ...

  5. elisp语法

    http://stackoverflow.com/questions/3855862/setq-and-defvar-in-lisp defvar, let, setq的语法分析: defvar in ...

  6. linux socket中的SO_REUSEADDR

    Welcome to the wonderful world of portability... or rather the lack of it. Before we start analyzing ...

  7. 第二百七十二、三天 how can I 坚持

    昨天加班回来都很晚了,也忘了些日志了.其实感觉加班也没什么啊,一个团队在一块说说闹闹,愉快的完成工作挺好. 今天是2015年的最后一天,2015的愿望啊,只怪自己太怂了.不怂会是什么结果. 其实更应该 ...

  8. My First Blog on cnblogs (现代程序设计 Homework-01)

    Hello CNBLOGS!Hello Everyone! 这是我的第一篇blog,所以这也是一篇试验性的blog. 这个学期我和很多同学一样选修了邹欣老师的现代程序设计这门专业课.第一次看到使用Gi ...

  9. 现代程序设计 homework-09

    现代程序设计 homework-09 这次作业是要求将homework-02做成一个可演示的应用,目的是为了让用户看到程序的计算步骤以及中间结果. 借此机会也学了一下JavaScript,感觉总结的地 ...

  10. 【转】并发编程之Operation Queue

    http://blog.xcodev.com/blog/2013/10/28/operation-queue-intro/ 随着移动设备的更新换代,移动设备的性能也不断提高,现在流行的CPU已经进入双 ...