自定义控件,较常用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. [转] AE中如何由IFeature 如何获取所对应的FeatureClass

    转载的原文 AE中如何由IFeature 如何获取所对应的FeatureClass   先获取FeatureClass,然后遍历Map中所有的FeatureLayer,然后比较 FeatureClas ...

  2. Tour

    题意: 给n个点的坐标,求形成的最短的闭合回路. 分析: 经典问题,dp[i][j]表示有1-i点再由j回到1点的最短距离,i点有两种情况,在去的路径上 dp[i][j]=min(dp[i][j],d ...

  3. HDU 5278 PowMod 数论公式推导

    题意:中文题自己看吧 分析:这题分两步 第一步:利用已知公式求出k: 第二步:求出k然后使用欧拉降幂公式即可,欧拉降幂公式不需要互质(第二步就是BZOJ3884原题了) 求k的话就需要构造了(引入官方 ...

  4. 简单把webdriver的find_element方法写成函数

    __author__ = 'jyd' from selenium.webdriver.common.by import By #driver webdriver实例化对象 #element 查询元素的 ...

  5. 基于51,人体红外感应和RC522的门禁系统

    总结一下最近学的东西,这两天学的东西,rfid门卡系统终于弄出来来了,这个程序算现在写过的比较满意的程序,大家可以参考参考 主函数: #include<reg52.h> #include& ...

  6. nodejs学习笔记之mongoDB

    这两天在学习nodejs,但是发现那本书nodejs入门指南上所用的好多方法都报错. 这里主要说下数据库部分 关于注册部分:书上创建数据库那里可能要小心点,用户名不存在的时候,下面调用save的对象要 ...

  7. python中yield用法

    在介绍yield前有必要先说明下Python中的迭代器(iterator)和生成器(constructor). 一.迭代器(iterator) 在Python中,for循环可以用于Python中的任何 ...

  8. bfs CCF2016第七次 游戏

    // bfs CCF2016第七次 游戏 // 思路: // O(300*100*100) // 直接暴搜 // 注意,同一格同一时间不能经过两次!!! #include <bits/stdc+ ...

  9. Static块详解

    首先,我们先看一段程序,代码如下: public class Father { public Father() //构造方法 { System.out.println(" 父类构造方法&qu ...

  10. ubuntu安装hudson

    因为hudson需要依赖java等,手动安装比较费劲 官方给出了一种很简单的解决方案:http://wiki.eclipse.org/Hudson-ci/Installing_Hudson_DEB s ...